// A port of Auburn's FastNoise to Rust.
// I really didn't like the noise libraries I could find, so I ported the one I like.
// Original code: https://github.com/Auburns/FastNoise
// The original is MIT licensed, so this is compatible.

use bracket_random::prelude::RandomNumberGenerator;

#[derive(PartialEq, Copy, Clone)]
/// Type of noise to generate
pub enum NoiseType {
    Value,
    ValueFractal,
    Perlin,
    PerlinFractal,
    Simplex,
    SimplexFractal,
    Cellular,
    WhiteNoise,
    Cubic,
    CubicFractal,
}

#[derive(PartialEq, Copy, Clone)]
/// Interpolation function to use
pub enum Interp {
    Linear,
    Hermite,
    Quintic,
}

#[derive(PartialEq, Copy, Clone)]
/// Fractal function to use
pub enum FractalType {
    FBM,
    Billow,
    RigidMulti,
}

#[derive(PartialEq, Copy, Clone)]
/// Cellular noise distance function to use
pub enum CellularDistanceFunction {
    Euclidean,
    Manhattan,
    Natural,
}

#[derive(PartialEq, Copy, Clone)]
/// What type of cellular noise result do you want
pub enum CellularReturnType {
    CellValue,
    Distance,
    Distance2,
    Distance2Add,
    Distance2Sub,
    Distance2Mul,
    Distance2Div,
}

pub struct FastNoise {
    rng: RandomNumberGenerator,
    seed: u64,
    frequency: f32,
    interp: Interp,
    noise_type: NoiseType,
    octaves: i32,
    lacunarity: f32,
    gain: f32,
    fractal_type: FractalType,
    cellular_distance_function: CellularDistanceFunction,
    cellular_return_type: CellularReturnType,
    cellular_distance_index: (i32, i32),
    cellular_jitter: f32,
    gradient_perturb_amp: f32,
    perm: Vec<u8>,
    perm12: Vec<u8>,
    fractal_bounding: f32,
}

// Constants that used to be at the top
const GRAD_X: [f32; 12] = [1.0, -1., 1., -1., 1., -1., 1., -1., 0., 0., 0., 0.];

const GRAD_Y: [f32; 12] = [1., 1., -1., -1., 0., 0., 0., 0., 1., -1., 1., -1.];

const GRAD_Z: [f32; 12] = [0., 0., 0., 0., 1., 1., -1., -1., 1., 1., -1., -1.];

#[allow(dead_code)]
const GRAD_4D: [f32; 128] = [
    0., 1., 1., 1., 0., 1., 1., -1., 0., 1., -1., 1., 0., 1., -1., -1., 0., -1., 1., 1., 0., -1.,
    1., -1., 0., -1., -1., 1., 0., -1., -1., -1., 1., 0., 1., 1., 1., 0., 1., -1., 1., 0., -1., 1.,
    1., 0., -1., -1., -1., 0., 1., 1., -1., 0., 1., -1., -1., 0., -1., 1., -1., 0., -1., -1., 1.,
    1., 0., 1., 1., 1., 0., -1., 1., -1., 0., 1., 1., -1., 0., -1., -1., 1., 0., 1., -1., 1., 0.,
    -1., -1., -1., 0., 1., -1., -1., 0., -1., 1., 1., 1., 0., 1., 1., -1., 0., 1., -1., 1., 0., 1.,
    -1., -1., 0., -1., 1., 1., 0., -1., 1., -1., 0., -1., -1., 1., 0., -1., -1., -1., 0.,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const VAL_LUT: [f32; 256] = [
    0.3490196078,
    0.4352941176,
    -0.4509803922,
    0.6392156863,
    0.5843137255,
    -0.1215686275,
    0.7176470588,
    -0.1058823529,
    0.3960784314,
    0.0431372549,
    -0.03529411765,
    0.3176470588,
    0.7254901961,
    0.137254902,
    0.8588235294,
    -0.8196078431,
    -0.7960784314,
    -0.3333333333,
    -0.6705882353,
    -0.3882352941,
    0.262745098,
    0.3254901961,
    -0.6470588235,
    -0.9215686275,
    -0.5294117647,
    0.5294117647,
    -0.4666666667,
    0.8117647059,
    0.3803921569,
    0.662745098,
    0.03529411765,
    -0.6156862745,
    -0.01960784314,
    -0.3568627451,
    -0.09019607843,
    0.7490196078,
    0.8352941176,
    -0.4039215686,
    -0.7490196078,
    0.9529411765,
    -0.0431372549,
    -0.9294117647,
    -0.6549019608,
    0.9215686275,
    -0.06666666667,
    -0.4431372549,
    0.4117647059,
    -0.4196078431,
    -0.7176470588,
    -0.8117647059,
    -0.2549019608,
    0.4901960784,
    0.9137254902,
    0.7882352941,
    -1.0,
    -0.4745098039,
    0.7960784314,
    0.8509803922,
    -0.6784313725,
    0.4588235294,
    1.0,
    -0.1843137255,
    0.4509803922,
    0.1450980392,
    -0.231372549,
    -0.968627451,
    -0.8588235294,
    0.4274509804,
    0.003921568627,
    -0.003921568627,
    0.2156862745,
    0.5058823529,
    0.7647058824,
    0.2078431373,
    -0.5921568627,
    0.5764705882,
    -0.1921568627,
    -0.937254902,
    0.08235294118,
    -0.08235294118,
    0.9058823529,
    0.8274509804,
    0.02745098039,
    -0.168627451,
    -0.7803921569,
    0.1137254902,
    -0.9450980392,
    0.2,
    0.01960784314,
    0.5607843137,
    0.2705882353,
    0.4431372549,
    -0.9607843137,
    0.6156862745,
    0.9294117647,
    -0.07450980392,
    0.3098039216,
    0.9921568627,
    -0.9137254902,
    -0.2941176471,
    -0.3411764706,
    -0.6235294118,
    -0.7647058824,
    -0.8901960784,
    0.05882352941,
    0.2392156863,
    0.7333333333,
    0.6549019608,
    0.2470588235,
    0.231372549,
    -0.3960784314,
    -0.05098039216,
    -0.2235294118,
    -0.3725490196,
    0.6235294118,
    0.7019607843,
    -0.8274509804,
    0.4196078431,
    0.07450980392,
    0.8666666667,
    -0.537254902,
    -0.5058823529,
    -0.8039215686,
    0.09019607843,
    -0.4823529412,
    0.6705882353,
    -0.7882352941,
    0.09803921569,
    -0.6078431373,
    0.8039215686,
    -0.6,
    -0.3254901961,
    -0.4117647059,
    -0.01176470588,
    0.4823529412,
    0.168627451,
    0.8745098039,
    -0.3647058824,
    -0.1607843137,
    0.568627451,
    -0.9921568627,
    0.9450980392,
    0.5137254902,
    0.01176470588,
    -0.1450980392,
    -0.5529411765,
    -0.5764705882,
    -0.1137254902,
    0.5215686275,
    0.1607843137,
    0.3725490196,
    -0.2,
    -0.7254901961,
    0.631372549,
    0.7098039216,
    -0.568627451,
    0.1294117647,
    -0.3098039216,
    0.7411764706,
    -0.8509803922,
    0.2549019608,
    -0.6392156863,
    -0.5607843137,
    -0.3176470588,
    0.937254902,
    0.9843137255,
    0.5921568627,
    0.6941176471,
    0.2862745098,
    -0.5215686275,
    0.1764705882,
    0.537254902,
    -0.4901960784,
    -0.4588235294,
    -0.2078431373,
    -0.2156862745,
    0.7725490196,
    0.3647058824,
    -0.2392156863,
    0.2784313725,
    -0.8823529412,
    0.8980392157,
    0.1215686275,
    0.1058823529,
    -0.8745098039,
    -0.9843137255,
    -0.7019607843,
    0.9607843137,
    0.2941176471,
    0.3411764706,
    0.1529411765,
    0.06666666667,
    -0.9764705882,
    0.3019607843,
    0.6470588235,
    -0.5843137255,
    0.05098039216,
    -0.5137254902,
    -0.137254902,
    0.3882352941,
    -0.262745098,
    -0.3019607843,
    -0.1764705882,
    -0.7568627451,
    0.1843137255,
    -0.5450980392,
    -0.4980392157,
    -0.2784313725,
    -0.9529411765,
    -0.09803921569,
    0.8901960784,
    -0.2862745098,
    -0.3803921569,
    0.5529411765,
    0.7803921569,
    -0.8352941176,
    0.6862745098,
    0.7568627451,
    0.4980392157,
    -0.6862745098,
    -0.8980392157,
    -0.7725490196,
    -0.7098039216,
    -0.2470588235,
    -0.9058823529,
    0.9764705882,
    0.1921568627,
    0.8431372549,
    -0.05882352941,
    0.3568627451,
    0.6078431373,
    0.5450980392,
    0.4039215686,
    -0.7333333333,
    -0.4274509804,
    0.6,
    0.6784313725,
    -0.631372549,
    -0.02745098039,
    -0.1294117647,
    0.3333333333,
    -0.8431372549,
    0.2235294118,
    -0.3490196078,
    -0.6941176471,
    0.8823529412,
    0.4745098039,
    0.4666666667,
    -0.7411764706,
    -0.2705882353,
    0.968627451,
    0.8196078431,
    -0.662745098,
    -0.4352941176,
    -0.8666666667,
    -0.1529411765,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const CELL_2D_X: [f32; 256] = [
    -0.6440658039,
    -0.08028078721,
    0.9983546168,
    0.9869492062,
    0.9284746418,
    0.6051097552,
    -0.794167404,
    -0.3488667991,
    -0.943136526,
    -0.9968171318,
    0.8740961579,
    0.1421139764,
    0.4282553608,
    -0.9986665833,
    0.9996760121,
    -0.06248383632,
    0.7120139305,
    0.8917660409,
    0.1094842955,
    -0.8730880804,
    0.2594811489,
    -0.6690063346,
    -0.9996834972,
    -0.8803608671,
    -0.8166554937,
    0.8955599676,
    -0.9398321388,
    0.07615451399,
    -0.7147270565,
    0.8707354457,
    -0.9580008579,
    0.4905965632,
    0.786775944,
    0.1079711577,
    0.2686638979,
    0.6113487322,
    -0.530770584,
    -0.7837268286,
    -0.8558691039,
    -0.5726093896,
    -0.9830740914,
    0.7087766359,
    0.6807027153,
    -0.08864708788,
    0.6704485923,
    -0.1350735482,
    -0.9381333003,
    0.9756655376,
    0.4231433671,
    -0.4959787385,
    0.1005554325,
    -0.7645857281,
    -0.5859053796,
    -0.9751154306,
    -0.6972258572,
    0.7907012002,
    -0.9109899213,
    -0.9584307894,
    -0.8269529333,
    0.2608264719,
    -0.7773760119,
    0.7606456974,
    -0.8961083758,
    -0.9838134719,
    0.7338893576,
    0.2161226729,
    0.673509891,
    -0.5512056873,
    0.6899744332,
    0.868004831,
    0.5897430311,
    -0.8950444221,
    -0.3595752773,
    0.8209486981,
    -0.2912360132,
    -0.9965011374,
    0.9766994634,
    0.738790822,
    -0.4730947722,
    0.8946479441,
    -0.6943628971,
    -0.6620468182,
    -0.0887255502,
    -0.7512250855,
    -0.5322986898,
    0.5226295385,
    0.2296318375,
    0.7915307344,
    -0.2756485999,
    -0.6900234522,
    0.07090588086,
    0.5981278485,
    0.3033429312,
    -0.7253142797,
    -0.9855874307,
    -0.1761843396,
    -0.6438468325,
    -0.9956136595,
    0.8541580762,
    -0.9999807666,
    -0.02152416253,
    -0.8705983095,
    -0.1197138014,
    -0.992107781,
    -0.9091181546,
    0.788610536,
    -0.994636402,
    0.4211256853,
    0.3110430857,
    -0.4031127839,
    0.7610684239,
    0.7685674467,
    0.152271555,
    -0.9364648723,
    0.1681333739,
    -0.3567427907,
    -0.418445483,
    -0.98774778,
    0.8705250765,
    -0.8911701067,
    -0.7315350966,
    0.6030885658,
    -0.4149130821,
    0.7585339481,
    0.6963196535,
    0.8332685012,
    -0.8086815232,
    0.7518116724,
    -0.3490535894,
    0.6972110903,
    -0.8795676928,
    -0.6442331882,
    0.6610236811,
    -0.9853565782,
    -0.590338458,
    0.09843602117,
    0.5646534882,
    -0.6023259233,
    -0.3539248861,
    0.5132728656,
    0.9380385118,
    -0.7599270056,
    -0.7425936564,
    -0.6679610562,
    -0.3018497816,
    0.814478266,
    0.03777430269,
    -0.7514235086,
    0.9662556939,
    -0.4720194901,
    -0.435054126,
    0.7091901235,
    0.929379209,
    0.9997434357,
    0.8306320299,
    -0.9434019629,
    -0.133133759,
    0.5048413216,
    0.3711995273,
    0.98552091,
    0.7401857005,
    -0.9999981398,
    -0.2144033253,
    0.4808624681,
    -0.413835885,
    0.644229305,
    0.9626648696,
    0.1833665934,
    0.5794129,
    0.01404446873,
    0.4388494993,
    0.5213612322,
    -0.5281609948,
    -0.9745306846,
    -0.9904373013,
    0.9100232252,
    -0.9914057719,
    0.7892627765,
    0.3364421659,
    -0.9416099764,
    0.7802732656,
    0.886302871,
    0.6524471291,
    0.5762186726,
    -0.08987644664,
    -0.2177026782,
    -0.9720345052,
    -0.05722538858,
    0.8105983127,
    0.3410261032,
    0.6452309645,
    -0.7810612152,
    0.9989395718,
    -0.808247815,
    0.6370177929,
    0.5844658772,
    0.2054070861,
    0.055960522,
    -0.995827561,
    0.893409165,
    -0.931516824,
    0.328969469,
    -0.3193837488,
    0.7314755657,
    -0.7913517714,
    -0.2204109786,
    0.9955900414,
    -0.7112353139,
    -0.7935008741,
    -0.9961918204,
    -0.9714163995,
    -0.9566188669,
    0.2748495632,
    -0.4681743221,
    -0.9614449642,
    0.585194072,
    0.4532946061,
    -0.9916113176,
    0.942479587,
    -0.9813704753,
    -0.6538429571,
    0.2923335053,
    -0.2246660704,
    -0.1800781949,
    -0.9581216256,
    0.552215082,
    -0.9296791922,
    0.643183699,
    0.9997325981,
    -0.4606920354,
    -0.2148721265,
    0.3482070809,
    0.3075517813,
    0.6274756393,
    0.8910881765,
    -0.6397771309,
    -0.4479080125,
    -0.5247665011,
    -0.8386507094,
    0.3901291416,
    0.1458336921,
    0.01624613149,
    -0.8273199879,
    0.5611100679,
    -0.8380219841,
    -0.9856122234,
    -0.861398618,
    0.6398413916,
    0.2694510795,
    0.4327334514,
    -0.9960265354,
    -0.939570655,
    -0.8846996446,
    0.7642113189,
    -0.7002080528,
    0.664508256,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const CELL_2D_Y: [f32; 256] = [
    0.7649700911,
    0.9967722885,
    0.05734160033,
    -0.1610318741,
    0.371395799,
    -0.7961420628,
    0.6076990492,
    -0.9371723195,
    0.3324056156,
    0.07972205329,
    -0.4857529277,
    -0.9898503007,
    0.9036577593,
    0.05162417479,
    -0.02545330525,
    -0.998045976,
    -0.7021653386,
    -0.4524967717,
    -0.9939885256,
    -0.4875625128,
    -0.9657481729,
    -0.7432567015,
    0.02515761212,
    0.4743044842,
    0.5771254669,
    0.4449408324,
    0.3416365773,
    0.9970960285,
    0.6994034849,
    0.4917517499,
    0.286765333,
    0.8713868327,
    0.6172387009,
    0.9941540269,
    0.9632339851,
    -0.7913613129,
    0.847515538,
    0.6211056739,
    0.5171924952,
    -0.8198283277,
    -0.1832084353,
    0.7054329737,
    0.7325597678,
    0.9960630973,
    0.7419559859,
    0.9908355749,
    -0.346274329,
    0.2192641299,
    -0.9060627411,
    -0.8683346653,
    0.9949314574,
    -0.6445220433,
    -0.8103794704,
    -0.2216977607,
    0.7168515217,
    0.612202264,
    -0.412428616,
    0.285325116,
    0.56227115,
    -0.9653857009,
    -0.6290361962,
    0.6491672535,
    0.443835306,
    -0.1791955706,
    -0.6792690269,
    -0.9763662173,
    0.7391782104,
    0.8343693968,
    0.7238337389,
    0.4965557504,
    0.8075909592,
    -0.4459769977,
    -0.9331160806,
    -0.5710019572,
    0.9566512346,
    -0.08357920318,
    0.2146116448,
    -0.6739348049,
    0.8810115417,
    0.4467718167,
    -0.7196250184,
    -0.749462481,
    0.9960561112,
    0.6600461127,
    -0.8465566164,
    -0.8525598897,
    -0.9732775654,
    0.6111293616,
    -0.9612584717,
    -0.7237870097,
    -0.9974830104,
    -0.8014006968,
    0.9528814544,
    -0.6884178931,
    -0.1691668301,
    0.9843571905,
    0.7651544003,
    -0.09355982605,
    -0.5200134429,
    -0.006202125807,
    -0.9997683284,
    0.4919944954,
    -0.9928084436,
    -0.1253880012,
    -0.4165383308,
    -0.6148930171,
    -0.1034332049,
    -0.9070022917,
    -0.9503958117,
    0.9151503065,
    -0.6486716073,
    0.6397687707,
    -0.9883386937,
    0.3507613761,
    0.9857642561,
    -0.9342026446,
    -0.9082419159,
    0.1560587169,
    0.4921240607,
    -0.453669308,
    0.6818037859,
    0.7976742329,
    0.9098610522,
    0.651633524,
    0.7177318024,
    -0.5528685241,
    0.5882467118,
    0.6593778956,
    0.9371027648,
    -0.7168658839,
    -0.4757737632,
    0.7648291307,
    0.7503650398,
    0.1705063456,
    -0.8071558121,
    -0.9951433815,
    -0.8253280792,
    -0.7982502628,
    0.9352738503,
    0.8582254747,
    -0.3465310238,
    0.65000842,
    -0.6697422351,
    0.7441962291,
    -0.9533555,
    0.5801940659,
    -0.9992862963,
    -0.659820211,
    0.2575848092,
    0.881588113,
    -0.9004043022,
    -0.7050172826,
    0.369126382,
    -0.02265088836,
    0.5568217228,
    -0.3316515286,
    0.991098079,
    -0.863212164,
    -0.9285531277,
    0.1695539323,
    -0.672402505,
    -0.001928841934,
    0.9767452145,
    -0.8767960349,
    0.9103515037,
    -0.7648324016,
    0.2706960452,
    -0.9830446035,
    0.8150341657,
    -0.9999013716,
    -0.8985605806,
    0.8533360801,
    0.8491442537,
    -0.2242541966,
    -0.1379635899,
    -0.4145572694,
    0.1308227633,
    0.6140555916,
    0.9417041303,
    -0.336705587,
    -0.6254387508,
    0.4631060578,
    -0.7578342456,
    -0.8172955655,
    -0.9959529228,
    -0.9760151351,
    0.2348380732,
    -0.9983612848,
    0.5856025746,
    -0.9400538266,
    -0.7639875669,
    0.6244544645,
    0.04604054566,
    0.5888424828,
    0.7708490978,
    -0.8114182882,
    0.9786766212,
    -0.9984329822,
    0.09125496582,
    -0.4492438803,
    -0.3636982357,
    0.9443405575,
    -0.9476254645,
    -0.6818676535,
    -0.6113610831,
    0.9754070948,
    -0.0938108173,
    -0.7029540015,
    -0.6085691109,
    -0.08718862881,
    -0.237381926,
    0.2913423132,
    0.9614872426,
    0.8836361266,
    -0.2749974196,
    -0.8108932717,
    -0.8913607575,
    0.129255541,
    -0.3342637104,
    -0.1921249337,
    -0.7566302845,
    -0.9563164339,
    -0.9744358146,
    0.9836522982,
    -0.2863615732,
    0.8337016872,
    0.3683701937,
    0.7657119102,
    -0.02312427772,
    0.8875600535,
    0.976642191,
    0.9374176384,
    0.9515313457,
    -0.7786361937,
    -0.4538302125,
    -0.7685604874,
    -0.8940796454,
    -0.8512462154,
    0.5446696133,
    0.9207601495,
    -0.9893091197,
    -0.9998680229,
    0.5617309299,
    -0.8277411985,
    0.545636467,
    0.1690223212,
    -0.5079295433,
    0.7685069899,
    -0.9630140787,
    0.9015219132,
    0.08905695279,
    -0.3423550559,
    -0.4661614943,
    -0.6449659371,
    0.7139388509,
    0.7472809229,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const CELL_3D_X: [f32; 256] = [
    0.3752498686,
    0.687188096,
    0.2248135212,
    0.6692006647,
    -0.4376476931,
    0.6139972552,
    0.9494563929,
    0.8065108882,
    -0.2218812853,
    0.8484661167,
    0.5551817596,
    0.2133903499,
    0.5195126593,
    -0.6440141975,
    -0.5192897331,
    -0.3697654077,
    -0.07927779647,
    0.4187757321,
    -0.750078731,
    0.6579554632,
    -0.6859803838,
    -0.6878407087,
    0.9490848347,
    0.5795829433,
    -0.5325976529,
    -0.1363699466,
    0.417665879,
    -0.9108236468,
    0.4438605427,
    0.819294887,
    -0.4033873915,
    -0.2817317705,
    0.3969665622,
    0.5323450134,
    -0.6833017297,
    0.3881436661,
    -0.7119144767,
    -0.2306979838,
    -0.9398873022,
    0.1701906676,
    -0.4261839496,
    -0.003712295499,
    -0.734675004,
    -0.3195046015,
    0.7345307424,
    0.9766246496,
    -0.02003735175,
    -0.4824156342,
    0.4245892007,
    0.9072427669,
    0.593346808,
    -0.8911762541,
    -0.7657571834,
    -0.5268198896,
    -0.8801903279,
    -0.6296409617,
    -0.09492481344,
    -0.4920470525,
    0.7307666154,
    -0.2514540636,
    -0.3356210347,
    -0.3522787894,
    0.87847885,
    -0.7424096346,
    0.5757585274,
    0.4519299338,
    0.6420368628,
    -0.1128478447,
    0.499874883,
    0.5291681739,
    -0.5098837195,
    0.5639583502,
    -0.8456386526,
    -0.9657134875,
    -0.576437342,
    -0.5666013014,
    0.5667702405,
    -0.481316582,
    0.7313389916,
    -0.3805628566,
    -0.6512675909,
    -0.2787156951,
    0.8648059114,
    -0.9730216276,
    -0.8335820906,
    0.2673159641,
    0.231150148,
    0.01286214638,
    0.6774953261,
    0.6542885718,
    -0.02545450161,
    0.2101238586,
    -0.5572105885,
    0.813705672,
    -0.7546026951,
    -0.2502500006,
    -0.9979289381,
    0.7024037039,
    0.08990874624,
    0.8170812432,
    0.4226980265,
    -0.2442153475,
    -0.9183326731,
    0.6068222411,
    0.818676691,
    -0.7236735282,
    -0.5383903295,
    -0.6269337242,
    -0.0939331121,
    0.9203878539,
    -0.7256396824,
    0.6292431149,
    0.4234156978,
    0.006685688024,
    -0.2598694113,
    0.6408036421,
    0.05899871622,
    0.7090281418,
    -0.5905222072,
    0.3128214264,
    -0.691925826,
    0.3634019349,
    -0.6772511147,
    -0.3204583896,
    -0.3906740409,
    -0.3342190395,
    -0.517779592,
    -0.6817711267,
    0.6422383105,
    0.4388482478,
    0.2968562611,
    -0.2019778353,
    0.6014865048,
    0.9519280722,
    0.3398889569,
    0.8179709354,
    0.2365522154,
    0.3262175096,
    -0.8060715954,
    -0.2068642503,
    0.6208057279,
    -0.5274282502,
    -0.3722334928,
    -0.8923412971,
    0.5341834201,
    -0.3663701513,
    -0.6114600319,
    0.5026307556,
    0.8396151729,
    0.9245042467,
    -0.7994843957,
    -0.5357200589,
    -0.6283359739,
    -0.61351886,
    -0.875632008,
    -0.5278879423,
    0.9087491985,
    -0.03500215466,
    -0.261365798,
    -0.579523541,
    -0.3765052689,
    -0.74398252,
    0.4257318052,
    -0.1214508921,
    0.8561809753,
    0.6802835104,
    -0.5452131039,
    -0.1997156478,
    0.4562348357,
    -0.811704301,
    0.67793962,
    -0.9237819106,
    0.6973511259,
    -0.5189506,
    0.5517320032,
    -0.396710831,
    0.5493762815,
    -0.2507853002,
    0.4788634005,
    0.387333516,
    -0.2176515694,
    0.6749832419,
    0.2148283022,
    -0.7521815872,
    0.4697000159,
    0.7890593699,
    -0.7606162952,
    0.01083397843,
    0.5254091908,
    -0.6748025877,
    0.751091524,
    0.05259056135,
    0.01889481232,
    -0.6037423727,
    -0.6542965129,
    0.08873301081,
    -0.6191345671,
    0.4331858488,
    -0.3858351946,
    -0.1429059747,
    0.4118221036,
    -0.6247153214,
    -0.611423014,
    0.5542939606,
    -0.9432768808,
    -0.4567870451,
    -0.7349133547,
    0.399304489,
    -0.7474927672,
    0.02589419753,
    0.783915821,
    0.6138668752,
    0.4276376047,
    -0.4347886353,
    0.02947841302,
    -0.833742746,
    0.3817221742,
    -0.8743368359,
    -0.3823443796,
    -0.6829243811,
    -0.3681903049,
    -0.367626833,
    -0.434583373,
    0.235891995,
    -0.6874880269,
    -0.5115661773,
    -0.5534962601,
    0.5632777056,
    0.686191532,
    -0.05095871588,
    -0.06865785057,
    -0.5975288531,
    -0.6429790056,
    -0.3729361548,
    0.2237917666,
    0.6046773225,
    -0.5041542295,
    -0.03972191174,
    0.7028828406,
    -0.5560856498,
    0.5898328456,
    -0.9308076766,
    0.4617069864,
    0.3190983137,
    0.9116567753,
    -0.45029554,
    0.3346334459,
    0.8525005645,
    0.2528483381,
    -0.8306630147,
    -0.6880390622,
    0.7448684026,
    -0.1963355843,
    -0.5900257974,
    0.9097057294,
    -0.2509196808,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const CELL_3D_Y: [f32; 256] = [
    -0.6760585049,
    -0.09136176499,
    0.1681325679,
    -0.6688468686,
    -0.4822753902,
    -0.7891068824,
    -0.1877509944,
    0.548470914,
    -0.463339443,
    -0.4050542082,
    0.3218158513,
    0.2546493823,
    -0.3753271935,
    0.4745384887,
    0.481254652,
    -0.8934416489,
    -0.6737085076,
    0.7469917228,
    0.3826230411,
    0.6751013678,
    -0.7248119515,
    -0.3224276742,
    -0.02076190936,
    -0.6404268166,
    -0.5292028444,
    0.7151414636,
    -0.6144655059,
    -0.369912124,
    0.6942067212,
    -0.4481558248,
    -0.6366894559,
    0.5956568471,
    0.564274539,
    0.7145584688,
    0.6871918316,
    0.5657918509,
    -0.6275978114,
    0.4146983062,
    0.2638993789,
    -0.792633138,
    0.5706133514,
    0.8606546462,
    0.6490900316,
    -0.8242699196,
    0.6765819124,
    0.1959534069,
    -0.8426769757,
    -0.5917672797,
    0.7517364266,
    0.03252559226,
    0.0883617105,
    0.4475064813,
    -0.1418643552,
    0.7343428473,
    0.3870192548,
    -0.7716703522,
    0.4839898327,
    0.7437439055,
    -0.5989573348,
    -0.8357068955,
    0.6086049038,
    0.9194627258,
    0.4718297238,
    -0.2650335884,
    -0.6470352599,
    -0.5555181303,
    0.1222351235,
    0.7802044684,
    -0.8636947022,
    -0.2341352163,
    0.683030874,
    -0.5005858287,
    0.2334616211,
    0.2576877608,
    0.6666816727,
    -0.7663996863,
    0.794201982,
    0.6189308788,
    0.6071033261,
    -0.4206058253,
    -0.3957336915,
    -0.8170257484,
    -0.1043240417,
    0.0002167596213,
    0.1816339018,
    -0.6838094939,
    -0.2495341969,
    -0.7116756954,
    -0.03361673621,
    -0.3350836431,
    0.2137186039,
    0.2557996786,
    0.7490117093,
    0.4942936549,
    -0.352686853,
    -0.3952445435,
    -0.0459964767,
    -0.7115787471,
    0.08022899756,
    0.5362268157,
    -0.8258613686,
    0.1114171723,
    0.3882823051,
    -0.7915404457,
    0.3250957662,
    0.6401346464,
    -0.2662724517,
    -0.6727907114,
    -0.994730818,
    -0.3596358977,
    0.2344610069,
    -0.6645215546,
    -0.7107590611,
    -0.4646617327,
    0.6717191355,
    0.5101893498,
    0.1185768238,
    0.236005093,
    -0.7811024061,
    0.5089325193,
    0.6073187658,
    -0.7930732557,
    -0.6822767155,
    0.3201532885,
    0.7545302807,
    0.1072664448,
    0.6784033173,
    -0.6595924967,
    0.7276509498,
    0.5586689436,
    -0.6498636788,
    0.6789333174,
    0.7105966551,
    -0.2872214155,
    0.496746217,
    -0.3880337977,
    0.7324070604,
    -0.9326634749,
    -0.5867839255,
    0.8003043651,
    -0.1631882481,
    -0.6796374681,
    -0.8066678503,
    0.4238177418,
    0.7715863549,
    0.5455367347,
    -0.03205115397,
    -0.6005545066,
    -0.5423640002,
    0.3569205906,
    -0.582071752,
    0.6407354361,
    0.7777142984,
    -0.09956428618,
    0.1100002681,
    0.8136349123,
    0.2923431904,
    0.9735794425,
    0.8324974864,
    -0.6179617717,
    -0.9248386523,
    -0.6448780771,
    -0.5274402761,
    -0.7862170565,
    0.2682099744,
    -0.5848777694,
    -0.6364561467,
    -0.7167402514,
    -0.8677012494,
    0.4205286707,
    -0.7007832749,
    0.243272451,
    -0.1899846085,
    -0.6146124977,
    -0.8093357692,
    -0.03545096987,
    -0.7191590868,
    0.7478645848,
    0.3623517328,
    0.8436992512,
    -0.2445711729,
    0.6897356637,
    -0.1708070787,
    0.4639272368,
    -0.7917186656,
    0.02980025428,
    0.6334156172,
    -0.9815544807,
    -0.2307217304,
    0.1080823318,
    0.5167601798,
    -0.845120016,
    0.441572562,
    0.5876789172,
    -0.6365908737,
    0.68350166,
    0.5849723959,
    0.1164114357,
    -0.7379813884,
    -0.9613237178,
    -0.9071943084,
    -0.7682111105,
    0.639074459,
    -0.619358298,
    0.2807257131,
    -0.01800868791,
    0.3776607289,
    0.7207567823,
    0.5536661486,
    -0.9974053117,
    -0.02047200006,
    -0.6739453804,
    -0.5607471297,
    0.8815553192,
    0.8275977415,
    0.3928902456,
    0.550991396,
    0.4247623676,
    -0.3436948871,
    -0.3653537677,
    0.3181702902,
    -0.6067173171,
    -0.8984128477,
    0.4220839766,
    0.7238407199,
    -0.7766913695,
    0.6460037842,
    0.2544775664,
    0.6488840578,
    0.805016833,
    -0.9183807036,
    0.4144046357,
    0.270587208,
    -0.8813684494,
    0.6985971877,
    -0.7795603017,
    -0.8624480731,
    0.5532697017,
    0.711179521,
    -0.7798160574,
    0.5225859041,
    0.1261859368,
    0.3398033582,
    -0.7472173667,
    -0.4032647119,
    -0.4246578154,
    0.8481212377,
    -0.2144838537,
    0.3431714491,
    0.5310188231,
    0.6682978632,
    0.3110433206,
    0.9263293599,
    -0.6155600569,
    0.07169784399,
    0.8985888773,
];

#[allow(clippy::unreadable_literal)]
#[allow(clippy::excessive_precision)]
const CELL_3D_Z: [f32; 256] = [
    -0.6341391283,
    -0.7207118346,
    0.9597866014,
    0.3237504235,
    -0.7588642466,
    -0.01782410481,
    0.2515593809,
    0.2207257205,
    -0.8579541106,
    0.3406410681,
    0.7669470462,
    -0.9431957648,
    0.7676171537,
    -0.6000491115,
    -0.7062096948,
    0.2550207115,
    0.7347325213,
    0.5163625202,
    -0.5394270162,
    0.3336656285,
    -0.0638635111,
    -0.6503195787,
    0.3143356798,
    -0.5039217245,
    0.6605180464,
    -0.6855479011,
    -0.6693185756,
    0.1832083647,
    -0.5666258437,
    0.3576482138,
    -0.6571949095,
    -0.7522101635,
    -0.7238865886,
    0.4538887323,
    0.2467106257,
    0.7274778869,
    0.3151170655,
    -0.8802293764,
    -0.2167232729,
    0.5854637865,
    0.7019741052,
    0.5091756071,
    0.1973189533,
    0.46743546,
    0.05197599597,
    0.088354718,
    0.5380464843,
    -0.6458224544,
    -0.5045952393,
    0.419347884,
    0.8000823542,
    -0.07445020656,
    -0.6272881641,
    -0.428020311,
    -0.2747382083,
    -0.08987283726,
    0.8699098354,
    0.4524761885,
    -0.3274603257,
    0.4882262167,
    -0.7189983256,
    0.1746079907,
    0.0751772698,
    -0.6152927202,
    0.4998474673,
    -0.6979677227,
    0.7568667263,
    -0.6152612058,
    0.06447140991,
    -0.8155744872,
    -0.5229602449,
    0.6567836838,
    -0.4799905631,
    0.03153534591,
    0.4724992466,
    -0.3026458097,
    -0.2191225827,
    -0.620692287,
    0.3107552588,
    0.8235670294,
    0.6474915988,
    -0.5047637941,
    0.4911488878,
    -0.2307138167,
    -0.5216800015,
    0.6789305939,
    0.9403734863,
    0.702390397,
    0.7347584625,
    0.6779567958,
    0.9765635805,
    -0.9436177661,
    -0.358465925,
    -0.3058706624,
    0.5533414464,
    -0.8838306897,
    0.04496841812,
    0.01687374963,
    -0.9927133148,
    -0.211752318,
    0.3732015249,
    0.9632990593,
    -0.07682417004,
    -0.07232213047,
    0.4733721775,
    0.2579229713,
    0.7995216286,
    0.3928189967,
    0.04107517667,
    0.1534542912,
    0.6468965045,
    0.4030684878,
    -0.5617300988,
    -0.885463029,
    0.693729985,
    -0.5736527866,
    -0.9911905409,
    -0.66451538,
    0.2028855685,
    0.8019541421,
    -0.3903877149,
    -0.4888495114,
    -0.2753714057,
    -0.8915202143,
    0.5273119089,
    0.9363714773,
    -0.5212228249,
    -0.31642672,
    0.2409440761,
    -0.703776404,
    -0.6996810411,
    -0.7058714505,
    -0.3650566783,
    0.1064744278,
    0.7985729102,
    0.424680257,
    -0.6384535592,
    0.1540161646,
    -0.07702731943,
    -0.5627789132,
    -0.7667919169,
    -0.509815999,
    0.4590525092,
    0.1552595611,
    0.345402042,
    0.7537656024,
    0.7906259247,
    -0.6218493452,
    0.02979350071,
    -0.1337893489,
    -0.1483818606,
    0.549965562,
    0.01882482408,
    -0.7833783002,
    0.4702855809,
    0.2435827372,
    0.2978428332,
    0.2256499906,
    0.4885036897,
    0.5312962584,
    0.05401156992,
    0.1749922158,
    -0.7352273018,
    0.6058980284,
    0.4416079111,
    0.4417378638,
    0.5455879807,
    -0.6681295324,
    0.1973431441,
    0.4053292055,
    0.2220375492,
    0.2957118467,
    0.6910913512,
    0.5940890106,
    -0.2014135283,
    -0.9172588213,
    -0.4254361401,
    -0.6146586825,
    -0.7996193253,
    -0.3716777111,
    -0.9448876842,
    -0.2620349924,
    0.9615995749,
    -0.4679683524,
    0.3905937144,
    0.613593722,
    0.1422937358,
    0.1908754211,
    0.8189704912,
    -0.7300408736,
    -0.4108776451,
    -0.5319834504,
    -0.8970265651,
    -0.5386359045,
    0.4082255906,
    0.7245356676,
    0.5239080873,
    -0.8937552226,
    -0.553637673,
    0.2354455182,
    -0.0860293075,
    -0.1399373318,
    -0.4666323327,
    0.5560157407,
    0.1772619533,
    -0.8893937725,
    -0.5632714576,
    -0.5666264959,
    -0.3670263736,
    -0.06717242579,
    0.6205295181,
    -0.4110536264,
    0.7090054553,
    0.183899597,
    -0.5605470555,
    0.3879565548,
    0.7420893903,
    -0.2347595118,
    -0.8577217497,
    0.6325590203,
    -0.8736152276,
    0.7048011129,
    -0.06317948268,
    0.8753285574,
    -0.05843650473,
    -0.3674922622,
    -0.5256624401,
    0.7861039337,
    0.3287714416,
    0.5910593099,
    -0.3896960134,
    0.6864605361,
    0.7164918431,
    -0.290014277,
    -0.6796169617,
    0.1632515592,
    0.04485347486,
    0.8320545697,
    0.01339408056,
    -0.2874989857,
    0.615630723,
    0.3430367014,
    0.8193658136,
    -0.5829600957,
    0.07911697781,
    0.7854296063,
    -0.4107442306,
    0.4766964066,
    -0.9045999527,
    -0.1673856787,
    0.2828077348,
    -0.5902737632,
    -0.321506229,
    -0.5224513133,
    -0.4090169985,
    -0.3599685311,
];

const FN_CELLULAR_INDEX_MAX: usize = 3;
const X_PRIME: i32 = 1619;
const Y_PRIME: i32 = 31337;
const Z_PRIME: i32 = 6971;
#[allow(dead_code)]
const W_PRIME: i32 = 1013;
const F3: f32 = 1.0 / 3.0;
const G3: f32 = 1.0 / 6.0;
#[allow(clippy::excessive_precision)]
#[allow(clippy::unreadable_literal)]
const SQRT3: f32 = 1.7320508075688772935274463415059;
const F2: f32 = 0.5 * (SQRT3 - 1.0);
const G2: f32 = (3.0 - SQRT3) / 6.0;
#[allow(dead_code)]
#[allow(clippy::excessive_precision)]
#[allow(clippy::unreadable_literal)]
const F4: f32 = (2.23606797749979 - 1.0) / 4.0;
#[allow(dead_code)]
#[allow(clippy::excessive_precision)]
#[allow(clippy::unreadable_literal)]
const G4: f32 = (5.0 - 2.23606797749979) / 20.0;
const CUBIC_3D_BOUNDING: f32 = 1.0 / (1.5 * 1.5 * 1.5);
const CUBIC_2D_BOUNDING: f32 = 1.0 / 1.5 * 1.5;

// Utility functions
fn fast_floor(f: f32) -> i32 {
    if f >= 0.0 {
        f as i32
    } else {
        f as i32 - 1
    }
}

fn fast_round(f: f32) -> i32 {
    if f >= 0.0 {
        (f + 0.5) as i32
    } else {
        (f - 0.5) as i32
    }
}

#[allow(dead_code)]
fn fast_abs(i: i32) -> i32 {
    i32::abs(i)
}

fn fast_abs_f(i: f32) -> f32 {
    f32::abs(i)
}

fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + t * (b - a)
}

fn interp_hermite_func(t: f32) -> f32 {
    t * t * (3. - 2. * t)
}

fn interp_quintic_func(t: f32) -> f32 {
    t * t * t * (t * (t * 6. - 15.) + 10.)
}

#[allow(clippy::many_single_char_names)]
fn cubic_lerp(a: f32, b: f32, c: f32, d: f32, t: f32) -> f32 {
    let p = (d - c) - (a - b);
    t * t * t * p + t * t * ((a - b) - p) + t * (c - a) + b
}

#[allow(clippy::unreadable_literal)]
#[allow(clippy::new_without_default)]
impl FastNoise {
    /// Creates a new noise instance, using simplex noise defaults.
    pub fn new() -> FastNoise {
        let mut noise = FastNoise {
            rng: RandomNumberGenerator::seeded(1337),
            seed: 1337,
            frequency: 1.0,
            interp: Interp::Quintic,
            noise_type: NoiseType::Simplex,
            octaves: 3,
            lacunarity: 2.0,
            gain: 0.5,
            fractal_type: FractalType::FBM,
            cellular_distance_function: CellularDistanceFunction::Euclidean,
            cellular_return_type: CellularReturnType::CellValue,
            cellular_distance_index: (0, 1),
            cellular_jitter: 0.45,
            gradient_perturb_amp: 1.0,
            perm: vec![0; 512],
            perm12: vec![0; 512],
            fractal_bounding: 0.0,
        };
        noise.set_seed(1337);
        noise.calculate_fractal_bounding();
        noise
    }

    /// Creates a new noise instance, using simplex noise defaults and specifying a random seed.
    pub fn seeded(seed: u64) -> FastNoise {
        let mut noise = FastNoise {
            rng: RandomNumberGenerator::seeded(seed),
            seed,
            frequency: 0.0,
            interp: Interp::Quintic,
            noise_type: NoiseType::Simplex,
            octaves: 3,
            lacunarity: 2.0,
            gain: 0.5,
            fractal_type: FractalType::FBM,
            cellular_distance_function: CellularDistanceFunction::Euclidean,
            cellular_return_type: CellularReturnType::CellValue,
            cellular_distance_index: (0, 1),
            cellular_jitter: 0.45,
            gradient_perturb_amp: 1.0,
            perm: vec![0; 512],
            perm12: vec![0; 512],
            fractal_bounding: 0.0,
        };
        noise.set_seed(seed);
        noise.calculate_fractal_bounding();
        noise
    }

    /// Re-seeds the noise system with a new seed.
    pub fn set_seed(&mut self, seed: u64) {
        self.seed = seed;
        self.rng = RandomNumberGenerator::seeded(seed);

        for i in 0..=255 {
            self.perm[i as usize] = i;
        }

        for j in 0..256 {
            let rng = self.rng.next_u64() % (256 - j);
            let k = rng + j;
            let l = self.perm[j as usize];
            self.perm[j as usize] = self.perm[k as usize];
            self.perm[j as usize + 256] = self.perm[k as usize];
            self.perm[k as usize] = l;
            self.perm12[j as usize] = self.perm[j as usize] % 12;
            self.perm12[j as usize + 256] = self.perm[j as usize] % 12;
        }
    }

    pub fn get_seed(&self) -> u64 {
        self.seed
    }
    pub fn set_frequency(&mut self, frequency: f32) {
        self.frequency = frequency
    }
    pub fn get_frequency(&self) -> f32 {
        self.frequency
    }
    pub fn set_interp(&mut self, interp: Interp) {
        self.interp = interp
    }
    pub fn get_interp(&self) -> Interp {
        self.interp
    }
    pub fn set_noise_type(&mut self, nt: NoiseType) {
        self.noise_type = nt
    }
    pub fn get_noise_type(&self) -> NoiseType {
        self.noise_type
    }
    pub fn set_fractal_octaves(&mut self, octaves: i32) {
        self.octaves = octaves;
        self.calculate_fractal_bounding();
    }
    pub fn get_fractal_octaves(&self) -> i32 {
        self.octaves
    }
    pub fn set_fractal_lacunarity(&mut self, lacunarity: f32) {
        self.lacunarity = lacunarity
    }
    pub fn get_fractal_lacunarity(&self) -> f32 {
        self.lacunarity
    }
    pub fn set_fractal_gain(&mut self, gain: f32) {
        self.gain = gain;
        self.calculate_fractal_bounding();
    }
    pub fn get_fractal_gain(&self) -> f32 {
        self.gain
    }
    pub fn set_fractal_type(&mut self, fractal_type: FractalType) {
        self.fractal_type = fractal_type
    }
    pub fn get_fractal_type(&self) -> FractalType {
        self.fractal_type
    }
    pub fn set_cellular_distance_function(
        &mut self,
        cellular_distance_function: CellularDistanceFunction,
    ) {
        self.cellular_distance_function = cellular_distance_function
    }
    pub fn get_cellular_distance_function(&self) -> CellularDistanceFunction {
        self.cellular_distance_function
    }
    pub fn set_cellular_return_type(&mut self, cellular_return_type: CellularReturnType) {
        self.cellular_return_type = cellular_return_type
    }
    pub fn get_cellular_return_type(&self) -> CellularReturnType {
        self.cellular_return_type
    }
    pub fn get_cellular_distance_indices(&self) -> (i32, i32) {
        self.cellular_distance_index
    }
    pub fn set_cellular_jitter(&mut self, jitter: f32) {
        self.cellular_jitter = jitter
    }
    pub fn get_cellular_jitter(&self) -> f32 {
        self.cellular_jitter
    }
    pub fn set_gradient_perterb_amp(&mut self, gradient_perturb_amp: f32) {
        self.gradient_perturb_amp = gradient_perturb_amp
    }
    pub fn get_gradient_perterb_amp(&self) -> f32 {
        self.gradient_perturb_amp
    }

    fn calculate_fractal_bounding(&mut self) {
        let mut amp: f32 = self.gain;
        let mut amp_fractal: f32 = 1.0;
        for _ in 0..self.octaves {
            amp_fractal += amp;
            amp *= self.gain;
        }
        self.fractal_bounding = 1.0 / amp_fractal;
    }

    pub fn set_cellular_distance_indices(&mut self, i1: i32, i2: i32) {
        self.cellular_distance_index.0 = i32::min(i1, i2);
        self.cellular_distance_index.1 = i32::max(i1, i2);

        self.cellular_distance_index.0 = i32::min(
            i32::max(self.cellular_distance_index.0, 0),
            FN_CELLULAR_INDEX_MAX as i32,
        );
        self.cellular_distance_index.1 = i32::min(
            i32::max(self.cellular_distance_index.1, 0),
            FN_CELLULAR_INDEX_MAX as i32,
        );
    }

    pub fn index2d_12(&self, offset: u8, x: i32, y: i32) -> u8 {
        self.perm12[(x & 0xff) as usize + self.perm[(y & 0xff) as usize + offset as usize] as usize]
    }

    pub fn index3d_12(&self, offset: u8, x: i32, y: i32, z: i32) -> u8 {
        self.perm12[(x as usize & 0xff)
            + self.perm
                [(y as usize & 0xff) + self.perm[(z as usize & 0xff) + offset as usize] as usize]
                as usize]
    }

    pub fn index4d_32(&self, offset: u8, x: i32, y: i32, z: i32, w: i32) -> u8 {
        self.perm[(x as usize & 0xff)
            + self.perm[(y as usize & 0xff)
                + self.perm[(z as usize & 0xff)
                    + self.perm[(w as usize & 0xff) + offset as usize] as usize]
                    as usize] as usize]
            & 31
    }

    pub fn index2d_256(&self, offset: u8, x: i32, y: i32) -> u8 {
        self.perm[(x as usize & 0xff) + self.perm[(y as usize & 0xff) + offset as usize] as usize]
    }

    pub fn index3d_256(&self, offset: u8, x: i32, y: i32, z: i32) -> u8 {
        self.perm[(x as usize & 0xff)
            + self.perm
                [(y as usize & 0xff) + self.perm[(z as usize & 0xff) + offset as usize] as usize]
                as usize]
    }

    pub fn index4d_256(&self, offset: u8, x: i32, y: i32, z: i32, w: i32) -> u8 {
        self.perm[(x as usize & 0xff)
            + self.perm[(y as usize & 0xff)
                + self.perm[(z as usize & 0xff)
                    + self.perm[(w as usize & 0xff) + offset as usize] as usize]
                    as usize] as usize]
    }

    fn val_coord_2d(&self, seed: i32, x: i32, y: i32) -> f32 {
        use std::num::Wrapping;

        let mut n = Wrapping(seed);
        n ^= Wrapping(X_PRIME) * Wrapping(x);
        n ^= Wrapping(Y_PRIME) * Wrapping(y);
        (n * n * n * Wrapping(60493i32)).0 as f32 / 2147483648.0
    }

    fn val_coord_3d(&self, seed: i32, x: i32, y: i32, z: i32) -> f32 {
        use std::num::Wrapping;

        let mut n = Wrapping(seed);
        n ^= Wrapping(X_PRIME) * Wrapping(x);
        n ^= Wrapping(Y_PRIME) * Wrapping(y);
        n ^= Wrapping(Z_PRIME) * Wrapping(z);

        (n * n * n * Wrapping(60493i32)).0 as f32 / 2147483648.0
    }

    #[allow(dead_code)]
    #[allow(clippy::many_single_char_names)]
    fn val_coord_4d(&self, seed: i32, x: i32, y: i32, z: i32, w: i32) -> f32 {
        use std::num::Wrapping;

        let mut n = Wrapping(seed);
        n ^= Wrapping(X_PRIME) * Wrapping(x);
        n ^= Wrapping(Y_PRIME) * Wrapping(y);
        n ^= Wrapping(Z_PRIME) * Wrapping(z);
        n ^= Wrapping(W_PRIME) * Wrapping(w);

        (n * n * n * Wrapping(60493i32)).0 as f32 / 2147483648.0
    }

    fn val_coord_2d_fast(&self, offset: u8, x: i32, y: i32) -> f32 {
        VAL_LUT[self.index2d_256(offset, x, y) as usize]
    }
    fn val_coord_3d_fast(&self, offset: u8, x: i32, y: i32, z: i32) -> f32 {
        VAL_LUT[self.index3d_256(offset, x, y, z) as usize]
    }

    fn grad_coord_2d(&self, offset: u8, x: i32, y: i32, xd: f32, yd: f32) -> f32 {
        let lut_pos = self.index2d_12(offset, x, y) as usize;
        xd * GRAD_X[lut_pos] + yd * GRAD_Y[lut_pos]
    }

    #[allow(clippy::too_many_arguments)]
    fn grad_coord_3d(&self, offset: u8, x: i32, y: i32, z: i32, xd: f32, yd: f32, zd: f32) -> f32 {
        let lut_pos = self.index3d_12(offset, x, y, z) as usize;
        xd * GRAD_X[lut_pos] + yd * GRAD_Y[lut_pos] + zd * GRAD_Z[lut_pos]
    }

    #[allow(dead_code)]
    #[allow(clippy::too_many_arguments)]
    fn grad_coord_4d(
        &self,
        offset: u8,
        x: i32,
        y: i32,
        z: i32,
        w: i32,
        xd: f32,
        yd: f32,
        zd: f32,
        wd: f32,
    ) -> f32 {
        let lut_pos = self.index4d_32(offset, x, y, z, w) as usize;
        xd * GRAD_4D[lut_pos]
            + yd * GRAD_4D[lut_pos + 1]
            + zd * GRAD_4D[lut_pos + 2]
            + wd * GRAD_4D[lut_pos + 3]
    }

    pub fn get_noise3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.noise_type {
            NoiseType::Value => self.single_value3d(0, x, y, z),
            NoiseType::ValueFractal => match self.fractal_type {
                FractalType::FBM => self.single_value_fractal_fbm3d(x, y, z),
                FractalType::Billow => self.single_value_fractal_billow3d(x, y, z),
                FractalType::RigidMulti => self.single_value_fractal_rigid_multi3d(x, y, z),
            },
            NoiseType::Perlin => self.single_perlin3d(0, x, y, z),
            NoiseType::PerlinFractal => match self.fractal_type {
                FractalType::FBM => self.single_perlin_fractal_fbm3d(x, y, z),
                FractalType::Billow => self.single_perlin_fractal_billow3d(x, y, z),
                FractalType::RigidMulti => self.single_perlin_fractal_rigid_multi3d(x, y, z),
            },
            NoiseType::Simplex => self.single_simplex3d(0, x, y, z),
            NoiseType::SimplexFractal => match self.fractal_type {
                FractalType::FBM => self.single_simplex_fractal_fbm3d(x, y, z),
                FractalType::Billow => self.single_simplex_fractal_billow3d(x, y, z),
                FractalType::RigidMulti => self.single_simplex_fractal_rigid_multi3d(x, y, z),
            },
            NoiseType::Cellular => match self.cellular_return_type {
                CellularReturnType::CellValue => self.single_cellular3d(x, y, z),
                CellularReturnType::Distance => self.single_cellular3d(x, y, z),
                _ => self.single_cellular_2edge3d(x, y, z),
            },
            NoiseType::WhiteNoise => self.get_white_noise3d(x, y, z),
            NoiseType::Cubic => self.single_cubic3d(0, x, y, z),
            NoiseType::CubicFractal => match self.fractal_type {
                FractalType::FBM => self.single_cubic_fractal_fbm3d(x, y, z),
                FractalType::Billow => self.single_cubic_fractal_billow3d(x, y, z),
                FractalType::RigidMulti => self.single_cubic_fractal_rigid_multi3d(x, y, z),
            },
        }
    }

    pub fn get_noise(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.noise_type {
            NoiseType::Value => self.single_value(0, x, y),
            NoiseType::ValueFractal => match self.fractal_type {
                FractalType::FBM => self.single_value_fractal_fbm(x, y),
                FractalType::Billow => self.single_value_fractal_billow(x, y),
                FractalType::RigidMulti => self.single_value_fractal_rigid_multi(x, y),
            },
            NoiseType::Perlin => self.single_perlin(0, x, y),
            NoiseType::PerlinFractal => match self.fractal_type {
                FractalType::FBM => self.single_perlin_fractal_fbm(x, y),
                FractalType::Billow => self.single_perlin_fractal_billow(x, y),
                FractalType::RigidMulti => self.single_perlin_fractal_rigid_multi(x, y),
            },
            NoiseType::Simplex => self.single_simplex(0, x, y),
            NoiseType::SimplexFractal => match self.fractal_type {
                FractalType::FBM => self.single_simplex_fractal_fbm(x, y),
                FractalType::Billow => self.single_simplex_fractal_billow(x, y),
                FractalType::RigidMulti => self.single_simplex_fractal_rigid_multi(x, y),
            },
            NoiseType::Cellular => match self.cellular_return_type {
                CellularReturnType::CellValue => self.single_cellular(x, y),
                CellularReturnType::Distance => self.single_cellular(x, y),
                _ => self.single_cellular_2edge(x, y),
            },
            NoiseType::WhiteNoise => self.get_white_noise(x, y),
            NoiseType::Cubic => self.single_cubic(0, x, y),
            NoiseType::CubicFractal => match self.fractal_type {
                FractalType::FBM => self.single_cubic_fractal_fbm(x, y),
                FractalType::Billow => self.single_cubic_fractal_billow(x, y),
                FractalType::RigidMulti => self.single_cubic_fractal_rigid_multi(x, y),
            },
        }
    }

    #[allow(dead_code)]
    fn get_white_noise4d(&self, x: f32, y: f32, z: f32, w: f32) -> f32 {
        let xc: i32 = x.to_bits() as i32;
        let yc: i32 = y.to_bits() as i32;
        let zc: i32 = z.to_bits() as i32;
        let wc: i32 = w.to_bits() as i32;

        self.val_coord_4d(
            self.seed as i32,
            xc ^ (xc as i32 >> 16),
            yc ^ (yc >> 16),
            zc ^ (zc >> 16),
            wc ^ (wc >> 16),
        )
    }

    fn get_white_noise3d(&self, x: f32, y: f32, z: f32) -> f32 {
        let xc: i32 = x.to_bits() as i32;
        let yc: i32 = y.to_bits() as i32;
        let zc: i32 = z.to_bits() as i32;

        self.val_coord_3d(
            self.seed as i32,
            xc ^ (xc >> 16),
            yc ^ (yc >> 16),
            zc ^ (zc >> 16),
        )
    }

    fn get_white_noise(&self, x: f32, y: f32) -> f32 {
        let xc: i32 = x.to_bits() as i32;
        let yc: i32 = y.to_bits() as i32;

        self.val_coord_2d(self.seed as i32, xc ^ (xc >> 16), yc ^ (yc >> 16))
    }

    #[allow(dead_code)]
    fn get_white_noise_int4d(&self, x: i32, y: i32, z: i32, w: i32) -> f32 {
        self.val_coord_4d(self.seed as i32, x, y, z, w)
    }

    #[allow(dead_code)]
    fn get_white_noise_int3d(&self, x: i32, y: i32, z: i32) -> f32 {
        self.val_coord_3d(self.seed as i32, x, y, z)
    }

    #[allow(dead_code)]
    fn get_white_noise_int(&self, x: i32, y: i32) -> f32 {
        self.val_coord_2d(self.seed as i32, x, y)
    }

    #[allow(dead_code)]
    // Value noise
    fn get_value_fractal3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_value_fractal_fbm3d(x, y, z),
            FractalType::Billow => self.single_value_fractal_billow3d(x, y, z),
            FractalType::RigidMulti => self.single_value_fractal_rigid_multi3d(x, y, z),
        }
    }

    #[allow(dead_code)]
    fn get_value_fractal(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_value_fractal_fbm(x, y),
            FractalType::Billow => self.single_value_fractal_billow(x, y),
            FractalType::RigidMulti => self.single_value_fractal_rigid_multi(x, y),
        }
    }

    fn single_value_fractal_fbm3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = self.single_value3d(self.perm[0], x, y, z);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_value3d(self.perm[i as usize], x, y, z) * amp;

            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_value_fractal_billow3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = fast_abs_f(self.single_value3d(self.perm[0], x, y, z)) * 2.0 - 1.0;
        let mut amp: f32 = 1.0;
        let mut i: i32 = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum +=
                (fast_abs_f(self.single_value3d(self.perm[i as usize], x, y, z)) * 2.0 - 1.0) * amp;

            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_value_fractal_rigid_multi3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = 1.0 - fast_abs_f(self.single_value3d(self.perm[0], x, y, z));
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_value3d(self.perm[i as usize], x, y, z))) * amp;

            i += 1;
        }
        sum
    }

    #[allow(dead_code)]
    fn get_value3d(&self, x: f32, y: f32, z: f32) -> f32 {
        self.single_value3d(
            0,
            x * self.frequency,
            y * self.frequency,
            z * self.frequency,
        )
    }

    fn single_value3d(&self, offset: u8, x: f32, y: f32, z: f32) -> f32 {
        let x0 = fast_floor(x);
        let y0 = fast_floor(y);
        let z0 = fast_floor(z);
        let x1 = x0 + 1;
        let y1 = y0 + 1;
        let z1 = z0 + 1;

        let xs: f32;
        let ys: f32;
        let zs: f32;
        match self.interp {
            Interp::Linear => {
                xs = x - x0 as f32;
                ys = y - y0 as f32;
                zs = z - z0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(x - x0 as f32);
                ys = interp_hermite_func(y - y0 as f32);
                zs = interp_hermite_func(z - z0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(x - x0 as f32);
                ys = interp_quintic_func(y - y0 as f32);
                zs = interp_quintic_func(z - z0 as f32);
            }
        }

        let xf00: f32 = lerp(
            self.val_coord_3d_fast(offset, x0, y0, z0),
            self.val_coord_3d_fast(offset, x1, y0, z0),
            xs,
        );
        let xf10: f32 = lerp(
            self.val_coord_3d_fast(offset, x0, y1, z0),
            self.val_coord_3d_fast(offset, x1, y1, z0),
            xs,
        );
        let xf01: f32 = lerp(
            self.val_coord_3d_fast(offset, x0, y0, z1),
            self.val_coord_3d_fast(offset, x1, y0, z1),
            xs,
        );
        let xf11: f32 = lerp(
            self.val_coord_3d_fast(offset, x0, y1, z1),
            self.val_coord_3d_fast(offset, x1, y1, z1),
            xs,
        );

        let yf0: f32 = lerp(xf00, xf10, ys);
        let yf1: f32 = lerp(xf01, xf11, ys);

        lerp(yf0, yf1, zs)
    }

    fn single_value_fractal_fbm(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum: f32 = self.single_value(self.perm[0], x, y);
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_value(self.perm[i as usize], x, y) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_value_fractal_billow(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum: f32 = fast_abs_f(self.single_value(self.perm[0], x, y)) * 2.0 - 1.0;
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            amp *= self.gain;
            sum += (fast_abs_f(self.single_value(self.perm[i as usize], x, y)) * 2.0 - 1.0) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_value_fractal_rigid_multi(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum: f32 = 1.0 - fast_abs_f(self.single_value(self.perm[0], x, y));
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_value(self.perm[i as usize], x, y))) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_value(&self, x: f32, y: f32) -> f32 {
        self.single_value(0, x * self.frequency, y * self.frequency)
    }

    fn single_value(&self, offset: u8, x: f32, y: f32) -> f32 {
        let x0 = fast_floor(x);
        let y0 = fast_floor(y);
        let x1 = x0 + 1;
        let y1 = y0 + 1;

        let xs: f32;
        let ys: f32;
        match self.interp {
            Interp::Linear => {
                xs = x - x0 as f32;
                ys = y - y0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(x - x0 as f32);
                ys = interp_hermite_func(y - y0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(x - x0 as f32);
                ys = interp_quintic_func(y - y0 as f32);
            }
        }

        let xf0 = lerp(
            self.val_coord_2d_fast(offset, x0, y0),
            self.val_coord_2d_fast(offset, x1, y0),
            xs,
        );
        let xf1 = lerp(
            self.val_coord_2d_fast(offset, x0, y1),
            self.val_coord_2d_fast(offset, x1, y1),
            xs,
        );

        lerp(xf0, xf1, ys)
    }

    // Perlin noise

    #[allow(dead_code)]
    fn get_perlin_fractal3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_perlin_fractal_fbm3d(x, y, z),
            FractalType::Billow => self.single_perlin_fractal_billow3d(x, y, z),
            FractalType::RigidMulti => self.single_perlin_fractal_rigid_multi3d(x, y, z),
        }
    }

    fn single_perlin_fractal_fbm3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = self.single_perlin3d(self.perm[0], x, y, z);
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_perlin3d(self.perm[i as usize], x, y, z) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_perlin_fractal_billow3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = fast_abs_f(self.single_perlin3d(self.perm[0], x, y, z)) * 2.0 - 1.0;
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += (fast_abs_f(self.single_perlin3d(self.perm[i as usize], x, y, z)) * 2.0 - 1.0)
                * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_perlin_fractal_rigid_multi3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum: f32 = 1.0 - fast_abs_f(self.single_perlin3d(self.perm[0], x, y, z));
        let mut amp: f32 = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_perlin3d(self.perm[i as usize], x, y, z))) * amp;

            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_perlin3d(&self, x: f32, y: f32, z: f32) -> f32 {
        self.single_perlin3d(
            0,
            x * self.frequency,
            y * self.frequency,
            z * self.frequency,
        )
    }

    fn single_perlin3d(&self, offset: u8, x: f32, y: f32, z: f32) -> f32 {
        let x0 = fast_floor(x);
        let y0 = fast_floor(y);
        let z0 = fast_floor(z);
        let x1 = x0 + 1;
        let y1 = y0 + 1;
        let z1 = z0 + 1;

        let xs: f32;
        let ys: f32;
        let zs: f32;

        match self.interp {
            Interp::Linear => {
                xs = x - x0 as f32;
                ys = y - y0 as f32;
                zs = z - z0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(x - x0 as f32);
                ys = interp_hermite_func(y - y0 as f32);
                zs = interp_hermite_func(z - z0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(x - x0 as f32);
                ys = interp_quintic_func(y - y0 as f32);
                zs = interp_quintic_func(z - z0 as f32);
            }
        }

        let xd0 = x - x0 as f32;
        let yd0 = y - y0 as f32;
        let zd0 = z - z0 as f32;
        let xd1 = xd0 - 1.0;
        let yd1 = yd0 - 1.0;
        let zd1 = zd0 - 1.0;

        let xf00 = lerp(
            self.grad_coord_3d(offset, x0, y0, z0, xd0, yd0, zd0),
            self.grad_coord_3d(offset, x1, y0, z0, xd1, yd0, zd0),
            xs,
        );
        let xf10 = lerp(
            self.grad_coord_3d(offset, x0, y1, z0, xd0, yd1, zd0),
            self.grad_coord_3d(offset, x1, y1, z0, xd1, yd1, zd0),
            xs,
        );
        let xf01 = lerp(
            self.grad_coord_3d(offset, x0, y0, z1, xd0, yd0, zd1),
            self.grad_coord_3d(offset, x1, y0, z1, xd1, yd0, zd1),
            xs,
        );
        let xf11 = lerp(
            self.grad_coord_3d(offset, x0, y1, z1, xd0, yd1, zd1),
            self.grad_coord_3d(offset, x1, y1, z1, xd1, yd1, zd1),
            xs,
        );

        let yf0 = lerp(xf00, xf10, ys);
        let yf1 = lerp(xf01, xf11, ys);

        lerp(yf0, yf1, zs)
    }

    #[allow(dead_code)]
    fn get_perlin_fractal(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_perlin_fractal_fbm(x, y),
            FractalType::Billow => self.single_perlin_fractal_billow(x, y),
            FractalType::RigidMulti => self.single_perlin_fractal_rigid_multi(x, y),
        }
    }

    fn single_perlin_fractal_fbm(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = self.single_perlin(self.perm[0], x, y);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_perlin(self.perm[i as usize], x, y) * amp;

            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_perlin_fractal_billow(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = fast_abs_f(self.single_perlin(self.perm[0], x, y)) * 2.0 - 1.0;
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += (fast_abs_f(self.single_perlin(self.perm[i as usize], x, y)) * 2.0 - 1.0) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_perlin_fractal_rigid_multi(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = 1.0 - fast_abs_f(self.single_perlin(self.perm[0], x, y));
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_perlin(self.perm[i as usize], x, y))) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_perlin(&self, x: f32, y: f32) -> f32 {
        self.single_perlin(0, x * self.frequency, y * self.frequency)
    }

    fn single_perlin(&self, offset: u8, x: f32, y: f32) -> f32 {
        let x0 = fast_floor(x);
        let y0 = fast_floor(y);
        let x1 = x0 + 1;
        let y1 = y0 + 1;

        let xs: f32;
        let ys: f32;

        match self.interp {
            Interp::Linear => {
                xs = x - x0 as f32;
                ys = y - y0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(x - x0 as f32);
                ys = interp_hermite_func(y - y0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(x - x0 as f32);
                ys = interp_quintic_func(y - y0 as f32);
            }
        }

        let xd0 = x - x0 as f32;
        let yd0 = y - y0 as f32;
        let xd1 = xd0 - 1.0;
        let yd1 = yd0 - 1.0;

        let xf0 = lerp(
            self.grad_coord_2d(offset, x0, y0, xd0, yd0),
            self.grad_coord_2d(offset, x1, y0, xd1, yd0),
            xs,
        );
        let xf1 = lerp(
            self.grad_coord_2d(offset, x0, y1, xd0, yd1),
            self.grad_coord_2d(offset, x1, y1, xd1, yd1),
            xs,
        );

        lerp(xf0, xf1, ys)
    }

    #[allow(dead_code)]
    // Simplex noise
    fn get_simplex_fractal3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_simplex_fractal_fbm3d(x, y, z),
            FractalType::Billow => self.single_simplex_fractal_billow3d(x, y, z),
            FractalType::RigidMulti => self.single_simplex_fractal_rigid_multi3d(x, y, z),
        }
    }

    fn single_simplex_fractal_fbm3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = self.single_simplex3d(self.perm[0], x, y, z);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_simplex3d(self.perm[i as usize], x, y, z) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_simplex_fractal_billow3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = fast_abs_f(self.single_simplex3d(self.perm[0], x, y, z)) * 2.0 - 1.0;
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += (fast_abs_f(self.single_simplex3d(self.perm[i as usize], x, y, z)) * 2.0 - 1.0)
                * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_simplex_fractal_rigid_multi3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = 1.0 - fast_abs_f(self.single_simplex3d(self.perm[0], x, y, z));
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_simplex3d(self.perm[i as usize], x, y, z))) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_simplex3d(&self, x: f32, y: f32, z: f32) -> f32 {
        self.single_simplex3d(
            0,
            x * self.frequency,
            y * self.frequency,
            z * self.frequency,
        )
    }

    #[allow(clippy::many_single_char_names)]
    #[allow(clippy::collapsible_if)]
    #[allow(clippy::suspicious_else_formatting)]
    fn single_simplex3d(&self, offset: u8, x: f32, y: f32, z: f32) -> f32 {
        let mut t: f32 = (x + y + z) * F3;
        let i = fast_floor(x + t);
        let j = fast_floor(y + t);
        let k = fast_floor(z + t);

        t = (i + j + k) as f32 * G3;
        let x0 = i as f32 - t;
        let y0 = j as f32 - t;
        let z0 = k as f32 - t;

        let x0 = x - x0;
        let y0 = y - y0;
        let z0 = z - z0;

        let i1: f32;
        let j1: f32;
        let k1: f32;
        let i2: f32;
        let j2: f32;
        let k2: f32;

        if x0 >= y0 {
            if y0 >= z0 {
                i1 = 1.;
                j1 = 0.;
                k1 = 0.;
                i2 = 1.;
                j2 = 1.;
                k2 = 0.;
            } else if x0 >= z0 {
                i1 = 1.;
                j1 = 0.;
                k1 = 0.;
                i2 = 1.;
                j2 = 0.;
                k2 = 1.;
            } else
            // x0 < z0
            {
                i1 = 0.;
                j1 = 0.;
                k1 = 1.;
                i2 = 1.;
                j2 = 0.;
                k2 = 1.;
            }
        } else
        // x0 < y0
        {
            if y0 < z0 {
                i1 = 0.;
                j1 = 0.;
                k1 = 1.;
                i2 = 0.;
                j2 = 1.;
                k2 = 1.;
            } else if x0 < z0 {
                i1 = 0.;
                j1 = 1.;
                k1 = 0.;
                i2 = 0.;
                j2 = 1.;
                k2 = 1.;
            } else
            // x0 >= z0
            {
                i1 = 0.;
                j1 = 1.;
                k1 = 0.;
                i2 = 1.;
                j2 = 1.;
                k2 = 0.;
            }
        }

        let x1 = x0 - i1 + G3;
        let y1 = y0 - j1 + G3;
        let z1 = z0 - k1 + G3;
        let x2 = x0 - i2 + 2.0 * G3;
        let y2 = y0 - j2 + 2.0 * G3;
        let z2 = z0 - k2 + 2.0 * G3;
        let x3 = x0 - 1. + 3.0 * G3;
        let y3 = y0 - 1. + 3.0 * G3;
        let z3 = z0 - 1. + 3.0 * G3;

        let n0;
        let n1;
        let n2;
        let n3;

        t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
        if t < 0. {
            n0 = 0.;
        } else {
            t *= t;
            n0 = t * t * self.grad_coord_3d(offset, i, j, k, x0, y0, z0);
        }

        t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
        if t < 0. {
            n1 = 0.
        } else {
            t *= t;
            n1 = t
                * t
                * self.grad_coord_3d(
                    offset,
                    i + i1 as i32,
                    j + j1 as i32,
                    k + k1 as i32,
                    x1,
                    y1,
                    z1,
                );
        }

        t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
        if t < 0. {
            n2 = 0.
        } else {
            t *= t;
            n2 = t
                * t
                * self.grad_coord_3d(
                    offset,
                    i + i2 as i32,
                    j + j2 as i32,
                    k + k2 as i32,
                    x2,
                    y2,
                    z2,
                );
        }

        t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
        if t < 0. {
            n3 = 0.
        } else {
            t *= t;
            n3 = t * t * self.grad_coord_3d(offset, i + 1, j + 1, k + 1, x3, y3, z3);
        }

        32.0 * (n0 + n1 + n2 + n3)
    }

    #[allow(dead_code)]
    fn get_simplex_fractal(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_simplex_fractal_fbm(x, y),
            FractalType::Billow => self.single_simplex_fractal_billow(x, y),
            FractalType::RigidMulti => self.single_simplex_fractal_rigid_multi(x, y),
        }
    }

    fn single_simplex_fractal_fbm(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = self.single_simplex(self.perm[0], x, y);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_simplex(self.perm[i as usize], x, y) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_simplex_fractal_billow(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = fast_abs_f(self.single_simplex(self.perm[0], x, y)) * 2.0 - 1.0;
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += (fast_abs_f(self.single_simplex(self.perm[i as usize], x, y)) * 2.0 - 1.0) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_simplex_fractal_rigid_multi(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = 1.0 - fast_abs_f(self.single_simplex(self.perm[0], x, y));
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - self.single_simplex(self.perm[i as usize], x, y)) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn single_simplex_fractal_blend(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = self.single_simplex(self.perm[0], x, y);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_simplex(self.perm[i as usize], x, y) * amp + 1.0;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    #[allow(dead_code)]
    fn get_simplex(&self, x: f32, y: f32) -> f32 {
        self.single_simplex(0, x * self.frequency, y * self.frequency)
    }

    #[allow(clippy::many_single_char_names)]
    fn single_simplex(&self, offset: u8, x: f32, y: f32) -> f32 {
        let mut t: f32 = (x + y) * F2;
        let i = fast_floor(x + t);
        let j = fast_floor(y + t);

        t = (i + j) as f32 * G2;
        let x0 = i as f32 - t;
        let y0 = j as f32 - t;

        let x0 = x - x0;
        let y0 = y - y0;

        let (i1, j1) = if x0 > y0 { (1, 0) } else { (0, 1) };

        let x1 = x0 - i1 as f32 + G2;
        let y1 = y0 - j1 as f32 + G2;
        let x2 = x0 - 1.0 + 2.0 * G2;
        let y2 = y0 - 1.0 + 2.0 * G2;

        let n0;
        let n1;
        let n2;

        t = 0.5 - x0 * x0 - y0 * y0;
        if t < 0. {
            n0 = 0.
        } else {
            t *= t;
            n0 = t * t * self.grad_coord_2d(offset, i, j, x0, y0);
        }

        t = 0.5 - x1 * x1 - y1 * y1;
        if t < 0. {
            n1 = 0.
        } else {
            t *= t;
            n1 = t * t * self.grad_coord_2d(offset, i + i1 as i32, j + j1 as i32, x1, y1);
        }

        t = 0.5 - x2 * x2 - y2 * y2;
        if t < 0. {
            n2 = 0.
        } else {
            t *= t;
            n2 = t * t * self.grad_coord_2d(offset, i + 1, j + 1, x2, y2);
        }

        70.0 * (n0 + n1 + n2)
    }

    #[allow(dead_code)]
    fn get_simplex_4d(&self, x: f32, y: f32, z: f32, w: f32) -> f32 {
        self.single_simplex4d(
            0,
            x * self.frequency,
            y * self.frequency,
            z * self.frequency,
            w * self.frequency,
        )
    }

    #[allow(dead_code)]
    fn greater_1_0(&self, n: i32, greater_than: i32) -> i32 {
        if n >= greater_than {
            1
        } else {
            0
        }
    }

    #[allow(dead_code)]
    #[allow(clippy::many_single_char_names)]
    fn single_simplex4d(&self, offset: u8, x: f32, y: f32, z: f32, w: f32) -> f32 {
        let n0: f32;
        let n1: f32;
        let n2: f32;
        let n3: f32;
        let n4: f32;

        let mut t = (x + y + z + w) * F4;
        let i = fast_floor(x + t) as f32;
        let j = fast_floor(y + t) as f32;
        let k = fast_floor(z + t) as f32;
        let l = fast_floor(w + t) as f32;
        t = (i + j + k + l) * G4;
        let x0 = i - t;
        let y0 = j - t;
        let z0 = k - t;
        let w0 = l - t;
        let x0 = x - x0;
        let y0 = y - y0;
        let z0 = z - z0;
        let w0 = w - w0;

        let mut rankx = 0;
        let mut ranky = 0;
        let mut rankz = 0;
        let mut rankw = 0;

        if x0 > y0 {
            rankx += 1;
        } else {
            ranky += 1;
        }
        if x0 > z0 {
            rankx += 1;
        } else {
            rankz += 1
        };
        if x0 > w0 {
            rankx += 1;
        } else {
            rankw += 1
        };
        if y0 > z0 {
            ranky += 1;
        } else {
            rankz += 1
        };
        if y0 > w0 {
            ranky += 1;
        } else {
            rankw += 1
        };
        if z0 > w0 {
            rankz += 1;
        } else {
            rankw += 1
        };

        let i1 = self.greater_1_0(rankx, 3);
        let j1 = self.greater_1_0(ranky, 3);
        let k1 = self.greater_1_0(rankz, 3);
        let l1 = self.greater_1_0(rankw, 3);

        let i2 = self.greater_1_0(rankx, 2);
        let j2 = self.greater_1_0(ranky, 2);
        let k2 = self.greater_1_0(rankz, 2);
        let l2 = self.greater_1_0(rankw, 2);

        let i3 = self.greater_1_0(rankx, 1);
        let j3 = self.greater_1_0(ranky, 1);
        let k3 = self.greater_1_0(rankz, 1);
        let l3 = self.greater_1_0(rankw, 1);

        let x1 = x0 - i1 as f32 + G4;
        let y1 = y0 - j1 as f32 + G4;
        let z1 = z0 - k1 as f32 + G4;
        let w1 = w0 - l1 as f32 + G4;
        let x2 = x0 - i2 as f32 + 2.0 * G4;
        let y2 = y0 - j2 as f32 + 2.0 * G4;
        let z2 = z0 - k2 as f32 + 2.0 * G4;
        let w2 = w0 - l2 as f32 + 2.0 * G4;
        let x3 = x0 - i3 as f32 + 3.0 * G4;
        let y3 = y0 - j3 as f32 + 3.0 * G4;
        let z3 = z0 - k3 as f32 + 3.0 * G4;
        let w3 = w0 - l3 as f32 + 3.0 * G4;
        let x4 = x0 - 1.0 + 4.0 * G4;
        let y4 = y0 - 1.0 + 4.0 * G4;
        let z4 = z0 - 1.0 + 4.0 * G4;
        let w4 = w0 - 1.0 + 4.0 * G4;

        t = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
        if t < 0.0 {
            n0 = 0.;
        } else {
            t *= t;
            n0 = t
                * t
                * self.grad_coord_4d(
                    offset, i as i32, j as i32, k as i32, l as i32, x0, y0, z0, w0,
                );
        }
        t = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
        if t < 0.0 {
            n1 = 0.;
        } else {
            t *= t;
            n1 = t
                * t
                * self.grad_coord_4d(
                    offset,
                    i as i32 + i1,
                    j as i32 + j1,
                    k as i32 + k1,
                    l as i32 + l1,
                    x1,
                    y1,
                    z1,
                    w1,
                );
        }
        t = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
        if t < 0.0 {
            n2 = 0.;
        } else {
            t *= t;
            n2 = t
                * t
                * self.grad_coord_4d(
                    offset,
                    i as i32 + i2,
                    j as i32 + j2,
                    k as i32 + k2,
                    l as i32 + l2,
                    x2,
                    y2,
                    z2,
                    w2,
                );
        }
        t = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
        if t < 0.0 {
            n3 = 0.;
        } else {
            t *= t;
            n3 = t
                * t
                * self.grad_coord_4d(
                    offset,
                    i as i32 + i3,
                    j as i32 + j3,
                    k as i32 + k3,
                    l as i32 + l3,
                    x3,
                    y3,
                    z3,
                    w3,
                );
        }
        t = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
        if t < 0.0 {
            n4 = 0.;
        } else {
            t *= t;
            n4 = t
                * t
                * self.grad_coord_4d(
                    offset,
                    i as i32 + 1,
                    j as i32 + 1,
                    k as i32 + 1,
                    l as i32 + 1,
                    x4,
                    y4,
                    z4,
                    w4,
                );
        }

        27.0 * (n0 + n1 + n2 + n3 + n4) as f32
    }

    #[allow(dead_code)]
    // Cubic Noise
    fn get_cubic_fractal3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_cubic_fractal_fbm3d(x, y, z),
            FractalType::Billow => self.single_cubic_fractal_billow3d(x, y, z),
            FractalType::RigidMulti => self.single_cubic_fractal_rigid_multi3d(x, y, z),
        }
    }

    fn single_cubic_fractal_fbm3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = self.single_cubic3d(self.perm[0], x, y, z);
        let mut amp = 1.0;
        let mut i = 1;
        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_cubic3d(self.perm[i as usize], x, y, z) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_cubic_fractal_billow3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = fast_abs_f(self.single_cubic3d(self.perm[0], x, y, z)) * 2.0 - 1.0;
        let mut amp = 1.0;
        let mut i = 1;
        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum +=
                (fast_abs_f(self.single_cubic3d(self.perm[i as usize], x, y, z)) * 2.0 - 1.0) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_cubic_fractal_rigid_multi3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        let mut sum = 1.0 - fast_abs_f(self.single_cubic3d(self.perm[0], x, y, z));
        let mut amp = 1.0;
        let mut i = 1;
        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;
            z *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_cubic3d(self.perm[i as usize], x, y, z))) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_cubic3d(&self, x: f32, y: f32, z: f32) -> f32 {
        self.single_cubic3d(
            0,
            x * self.frequency,
            y * self.frequency,
            z * self.frequency,
        )
    }

    fn single_cubic3d(&self, offset: u8, x: f32, y: f32, z: f32) -> f32 {
        let x1 = fast_floor(x);
        let y1 = fast_floor(y);
        let z1 = fast_floor(z);

        let x0 = x1 - 1;
        let y0 = y1 - 1;
        let z0 = z1 - 1;
        let x2 = x1 + 1;
        let y2 = y1 + 1;
        let z2 = z1 + 1;
        let x3 = x1 + 2;
        let y3 = y1 + 2;
        let z3 = z1 + 2;

        let xs = x - x1 as f32;
        let ys = y - y1 as f32;
        let zs = z - z1 as f32;

        cubic_lerp(
            cubic_lerp(
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y0, z0),
                    self.val_coord_3d_fast(offset, x1, y0, z0),
                    self.val_coord_3d_fast(offset, x2, y0, z0),
                    self.val_coord_3d_fast(offset, x3, y0, z0),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y1, z0),
                    self.val_coord_3d_fast(offset, x1, y1, z0),
                    self.val_coord_3d_fast(offset, x2, y1, z0),
                    self.val_coord_3d_fast(offset, x3, y1, z0),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y2, z0),
                    self.val_coord_3d_fast(offset, x1, y2, z0),
                    self.val_coord_3d_fast(offset, x2, y2, z0),
                    self.val_coord_3d_fast(offset, x3, y2, z0),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y3, z0),
                    self.val_coord_3d_fast(offset, x1, y3, z0),
                    self.val_coord_3d_fast(offset, x2, y3, z0),
                    self.val_coord_3d_fast(offset, x3, y3, z0),
                    xs,
                ),
                ys,
            ),
            cubic_lerp(
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y0, z1),
                    self.val_coord_3d_fast(offset, x1, y0, z1),
                    self.val_coord_3d_fast(offset, x2, y0, z1),
                    self.val_coord_3d_fast(offset, x3, y0, z1),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y1, z1),
                    self.val_coord_3d_fast(offset, x1, y1, z1),
                    self.val_coord_3d_fast(offset, x2, y1, z1),
                    self.val_coord_3d_fast(offset, x3, y1, z1),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y2, z1),
                    self.val_coord_3d_fast(offset, x1, y2, z1),
                    self.val_coord_3d_fast(offset, x2, y2, z1),
                    self.val_coord_3d_fast(offset, x3, y2, z1),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y3, z1),
                    self.val_coord_3d_fast(offset, x1, y3, z1),
                    self.val_coord_3d_fast(offset, x2, y3, z1),
                    self.val_coord_3d_fast(offset, x3, y3, z1),
                    xs,
                ),
                ys,
            ),
            cubic_lerp(
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y0, z2),
                    self.val_coord_3d_fast(offset, x1, y0, z2),
                    self.val_coord_3d_fast(offset, x2, y0, z2),
                    self.val_coord_3d_fast(offset, x3, y0, z2),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y1, z2),
                    self.val_coord_3d_fast(offset, x1, y1, z2),
                    self.val_coord_3d_fast(offset, x2, y1, z2),
                    self.val_coord_3d_fast(offset, x3, y1, z2),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y2, z2),
                    self.val_coord_3d_fast(offset, x1, y2, z2),
                    self.val_coord_3d_fast(offset, x2, y2, z2),
                    self.val_coord_3d_fast(offset, x3, y2, z2),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y3, z2),
                    self.val_coord_3d_fast(offset, x1, y3, z2),
                    self.val_coord_3d_fast(offset, x2, y3, z2),
                    self.val_coord_3d_fast(offset, x3, y3, z2),
                    xs,
                ),
                ys,
            ),
            cubic_lerp(
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y0, z3),
                    self.val_coord_3d_fast(offset, x1, y0, z3),
                    self.val_coord_3d_fast(offset, x2, y0, z3),
                    self.val_coord_3d_fast(offset, x3, y0, z3),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y1, z3),
                    self.val_coord_3d_fast(offset, x1, y1, z3),
                    self.val_coord_3d_fast(offset, x2, y1, z3),
                    self.val_coord_3d_fast(offset, x3, y1, z3),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y2, z3),
                    self.val_coord_3d_fast(offset, x1, y2, z3),
                    self.val_coord_3d_fast(offset, x2, y2, z3),
                    self.val_coord_3d_fast(offset, x3, y2, z3),
                    xs,
                ),
                cubic_lerp(
                    self.val_coord_3d_fast(offset, x0, y3, z3),
                    self.val_coord_3d_fast(offset, x1, y3, z3),
                    self.val_coord_3d_fast(offset, x2, y3, z3),
                    self.val_coord_3d_fast(offset, x3, y3, z3),
                    xs,
                ),
                ys,
            ),
            zs,
        ) * CUBIC_3D_BOUNDING
    }

    #[allow(dead_code)]
    fn get_cubic_fractal(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.fractal_type {
            FractalType::FBM => self.single_cubic_fractal_fbm(x, y),
            FractalType::Billow => self.single_cubic_fractal_billow(x, y),
            FractalType::RigidMulti => self.single_cubic_fractal_rigid_multi(x, y),
        }
    }

    fn single_cubic_fractal_fbm(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = self.single_cubic(self.perm[0], x, y);
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += self.single_cubic(self.perm[i as usize], x, y) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_cubic_fractal_billow(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = fast_abs_f(self.single_cubic(self.perm[0], x, y)) * 2.0 - 1.0;
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum += (fast_abs_f(self.single_cubic(self.perm[i as usize], x, y)) * 2.0 - 1.0) * amp;
            i += 1;
        }

        sum * self.fractal_bounding
    }

    fn single_cubic_fractal_rigid_multi(&self, mut x: f32, mut y: f32) -> f32 {
        let mut sum = 1.0 - fast_abs_f(self.single_cubic(self.perm[0], x, y));
        let mut amp = 1.0;
        let mut i = 1;

        while i < self.octaves {
            x *= self.lacunarity;
            y *= self.lacunarity;

            amp *= self.gain;
            sum -= (1.0 - fast_abs_f(self.single_cubic(self.perm[i as usize], x, y))) * amp;
            i += 1;
        }

        sum
    }

    #[allow(dead_code)]
    fn get_cubic(&self, x: f32, y: f32) -> f32 {
        self.single_cubic(0, x * self.frequency, y * self.frequency)
    }

    fn single_cubic(&self, offset: u8, x: f32, y: f32) -> f32 {
        let x1 = fast_floor(x);
        let y1 = fast_floor(y);

        let x0 = x1 - 1;
        let y0 = y1 - 1;
        let x2 = x1 + 1;
        let y2 = y1 + 1;
        let x3 = x1 + 2;
        let y3 = y1 + 2;

        let xs = x - x1 as f32;
        let ys = y - y1 as f32;

        cubic_lerp(
            cubic_lerp(
                self.val_coord_2d_fast(offset, x0, y0),
                self.val_coord_2d_fast(offset, x1, y0),
                self.val_coord_2d_fast(offset, x2, y0),
                self.val_coord_2d_fast(offset, x3, y0),
                xs,
            ),
            cubic_lerp(
                self.val_coord_2d_fast(offset, x0, y1),
                self.val_coord_2d_fast(offset, x1, y1),
                self.val_coord_2d_fast(offset, x2, y1),
                self.val_coord_2d_fast(offset, x3, y1),
                xs,
            ),
            cubic_lerp(
                self.val_coord_2d_fast(offset, x0, y2),
                self.val_coord_2d_fast(offset, x1, y2),
                self.val_coord_2d_fast(offset, x2, y2),
                self.val_coord_2d_fast(offset, x3, y2),
                xs,
            ),
            cubic_lerp(
                self.val_coord_2d_fast(offset, x0, y3),
                self.val_coord_2d_fast(offset, x1, y3),
                self.val_coord_2d_fast(offset, x2, y3),
                self.val_coord_2d_fast(offset, x3, y3),
                xs,
            ),
            ys,
        ) * CUBIC_2D_BOUNDING
    }

    #[allow(dead_code)]
    // Cellular Noise
    fn get_cellular3d(&self, mut x: f32, mut y: f32, mut z: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;
        z *= self.frequency;

        match self.cellular_return_type {
            CellularReturnType::CellValue => self.single_cellular3d(x, y, z),
            CellularReturnType::Distance => self.single_cellular3d(x, y, z),
            _ => self.single_cellular_2edge3d(x, y, z),
        }
    }

    fn single_cellular3d(&self, x: f32, y: f32, z: f32) -> f32 {
        let xr = fast_round(x);
        let yr = fast_round(y);
        let zr = fast_round(z);

        let mut distance: f32 = 999999.0;
        let mut xc: i32 = 0;
        let mut yc: i32 = 0;
        let mut zc: i32 = 0;

        match self.cellular_distance_function {
            CellularDistanceFunction::Euclidean => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos: u8 = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance = vec_x * vec_x + vec_y * vec_y + vec_z * vec_z;

                            if new_distance < distance {
                                distance = new_distance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
            }
            CellularDistanceFunction::Manhattan => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos: u8 = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance =
                                fast_abs_f(vec_x) + fast_abs_f(vec_y) + fast_abs_f(vec_z);

                            if new_distance < distance {
                                distance = new_distance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
            }
            CellularDistanceFunction::Natural => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos: u8 = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance =
                                (fast_abs_f(vec_x) + fast_abs_f(vec_y) + fast_abs_f(vec_z))
                                    + (vec_x * vec_x + vec_y * vec_y + vec_z * vec_z);

                            if new_distance < distance {
                                distance = new_distance;
                                xc = xi;
                                yc = yi;
                                zc = zi;
                            }
                        }
                    }
                }
            }
        }

        //let lut_pos : u8;
        match self.cellular_return_type {
            CellularReturnType::CellValue => self.val_coord_3d(self.seed as i32, xc, yc, zc),
            CellularReturnType::Distance => distance,
            _ => 0.0,
        }
    }

    fn single_cellular_2edge3d(&self, x: f32, y: f32, z: f32) -> f32 {
        let xr = fast_round(x);
        let yr = fast_round(y);
        let zr = fast_round(z);

        let mut distance: Vec<f32> = vec![999999.0; FN_CELLULAR_INDEX_MAX + 1];
        //FN_DECIMAL distance[FN_CELLULAR_INDEX_MAX+1] = { 999999,999999,999999,999999 };

        match self.cellular_distance_function {
            CellularDistanceFunction::Euclidean => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos: u8 = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance = vec_x * vec_x + vec_y * vec_y + vec_z * vec_z;

                            for i in (0..self.cellular_distance_index.1).rev() {
                                distance[i as usize] = f32::max(
                                    f32::min(distance[i as usize], new_distance),
                                    distance[i as usize - 1],
                                );
                            }
                            distance[0] = f32::min(distance[0], new_distance);
                        }
                    }
                }
            }
            CellularDistanceFunction::Manhattan => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance =
                                fast_abs_f(vec_x) + fast_abs_f(vec_y) + fast_abs_f(vec_z);

                            for i in (0..=self.cellular_distance_index.1).rev() {
                                distance[i as usize] = f32::max(
                                    f32::min(distance[i as usize], new_distance),
                                    distance[i as usize - 1],
                                );
                            }
                            distance[0] = f32::min(distance[0], new_distance);
                        }
                    }
                }
            }
            CellularDistanceFunction::Natural => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        for zi in zr - 1..zr + 2 {
                            let lut_pos = self.index3d_256(0, xi, yi, zi);

                            let vec_x =
                                xi as f32 - x + CELL_3D_X[lut_pos as usize] * self.cellular_jitter;
                            let vec_y =
                                yi as f32 - y + CELL_3D_Y[lut_pos as usize] * self.cellular_jitter;
                            let vec_z =
                                zi as f32 - z + CELL_3D_Z[lut_pos as usize] * self.cellular_jitter;

                            let new_distance =
                                (fast_abs_f(vec_x) + fast_abs_f(vec_y) + fast_abs_f(vec_z))
                                    + (vec_x * vec_x + vec_y * vec_y + vec_z * vec_z);

                            for i in (0..=self.cellular_distance_index.1).rev() {
                                distance[i as usize] = f32::max(
                                    f32::min(distance[i as usize], new_distance),
                                    distance[i as usize - 1],
                                );
                            }
                            distance[0] = f32::min(distance[0], new_distance);
                        }
                    }
                }
            }
        }

        match self.cellular_return_type {
            CellularReturnType::Distance2 => distance[self.cellular_distance_index.1 as usize],
            CellularReturnType::Distance2Add => {
                distance[self.cellular_distance_index.1 as usize]
                    + distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Sub => {
                distance[self.cellular_distance_index.1 as usize]
                    - distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Mul => {
                distance[self.cellular_distance_index.1 as usize]
                    * distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Div => {
                distance[self.cellular_distance_index.0 as usize]
                    / distance[self.cellular_distance_index.1 as usize]
            }
            _ => 0.0,
        }
    }

    #[allow(dead_code)]
    fn get_cellular(&self, mut x: f32, mut y: f32) -> f32 {
        x *= self.frequency;
        y *= self.frequency;

        match self.cellular_return_type {
            CellularReturnType::CellValue => self.single_cellular(x, y),
            CellularReturnType::Distance => self.single_cellular(x, y),
            _ => self.single_cellular_2edge(x, y),
        }
    }

    fn single_cellular(&self, x: f32, y: f32) -> f32 {
        let xr = fast_round(x);
        let yr = fast_round(y);

        let mut distance: f32 = 999999.0;

        match self.cellular_distance_function {
            CellularDistanceFunction::Euclidean => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos: u8 = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = vec_x * vec_x + vec_y * vec_y;

                        if new_distance < distance {
                            distance = new_distance;
                        }
                    }
                }
            }
            CellularDistanceFunction::Manhattan => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos: u8 = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = fast_abs_f(vec_x) + fast_abs_f(vec_y);

                        if new_distance < distance {
                            distance = new_distance;
                        }
                    }
                }
            }
            CellularDistanceFunction::Natural => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos: u8 = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = (fast_abs_f(vec_x) + fast_abs_f(vec_y))
                            + (vec_x * vec_x + vec_y * vec_y);

                        if new_distance < distance {
                            distance = new_distance;
                        }
                    }
                }
            }
        }

        //let lut_pos : u8;
        match self.cellular_return_type {
            CellularReturnType::CellValue => {
                self.val_coord_2d(self.seed as i32, x as i32, y as i32)
            }
            _ => 0.0,
        }
    }

    fn single_cellular_2edge(&self, x: f32, y: f32) -> f32 {
        let xr = fast_round(x);
        let yr = fast_round(y);

        let mut distance: Vec<f32> = vec![999999.0; FN_CELLULAR_INDEX_MAX + 1];

        match self.cellular_distance_function {
            CellularDistanceFunction::Euclidean => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = vec_x * vec_x + vec_y * vec_y;

                        for i in (0..=self.cellular_distance_index.1).rev() {
                            distance[i as usize] = f32::max(
                                f32::min(distance[i as usize], new_distance),
                                distance[i as usize - 1],
                            );
                        }
                        distance[0] = f32::min(distance[0], new_distance);
                    }
                }
            }
            CellularDistanceFunction::Manhattan => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = fast_abs_f(vec_x) + fast_abs_f(vec_y);

                        for i in (0..=self.cellular_distance_index.1).rev() {
                            distance[i as usize] = f32::max(
                                f32::min(distance[i as usize], new_distance),
                                distance[i as usize - 1],
                            );
                        }
                        distance[0] = f32::min(distance[0], new_distance);
                    }
                }
            }
            CellularDistanceFunction::Natural => {
                for xi in xr - 1..xr + 2 {
                    for yi in yr - 1..yr + 2 {
                        let lut_pos = self.index2d_256(0, xi, yi);

                        let vec_x =
                            xi as f32 - x + CELL_2D_X[lut_pos as usize] * self.cellular_jitter;
                        let vec_y =
                            yi as f32 - y + CELL_2D_Y[lut_pos as usize] * self.cellular_jitter;

                        let new_distance = (fast_abs_f(vec_x) + fast_abs_f(vec_y))
                            + (vec_x * vec_x + vec_y * vec_y);

                        for i in (0..=self.cellular_distance_index.1).rev() {
                            distance[i as usize] = f32::max(
                                f32::min(distance[i as usize], new_distance),
                                distance[i as usize - 1],
                            );
                        }
                        distance[0] = f32::min(distance[0], new_distance);
                    }
                }
            }
        }

        match self.cellular_return_type {
            CellularReturnType::Distance2 => distance[self.cellular_distance_index.0 as usize],
            CellularReturnType::Distance2Add => {
                distance[self.cellular_distance_index.1 as usize]
                    + distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Sub => {
                distance[self.cellular_distance_index.1 as usize]
                    - distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Mul => {
                distance[self.cellular_distance_index.1 as usize]
                    * distance[self.cellular_distance_index.0 as usize]
            }
            CellularReturnType::Distance2Div => {
                distance[self.cellular_distance_index.0 as usize]
                    / distance[self.cellular_distance_index.1 as usize]
            }
            _ => 0.0,
        }
    }

    #[allow(dead_code)]
    fn gradient_perturb3d(&self, x: &mut f32, y: &mut f32, z: &mut f32) {
        self.single_gradient_perturb3d(0, self.gradient_perturb_amp, self.frequency, x, y, z);
    }

    #[allow(dead_code)]
    fn gradient_perturb_fractal3d(&self, x: &mut f32, y: &mut f32, z: &mut f32) {
        let mut amp = self.gradient_perturb_amp * self.fractal_bounding;
        let mut freq = self.frequency;
        let mut i = 1;

        self.single_gradient_perturb3d(self.perm[0], amp, self.frequency, x, y, z);

        while i < self.octaves {
            freq *= self.lacunarity;
            amp *= self.gain;
            self.single_gradient_perturb3d(self.perm[i as usize], amp, freq, x, y, z);

            i += 1;
        }
    }

    #[allow(dead_code)]
    fn single_gradient_perturb3d(
        &self,
        offset: u8,
        warp_amp: f32,
        frequency: f32,
        x: &mut f32,
        y: &mut f32,
        z: &mut f32,
    ) {
        let xf = *x * frequency;
        let yf = *y * frequency;
        let zf = *z * frequency;

        let x0 = fast_floor(xf);
        let y0 = fast_floor(yf);
        let z0 = fast_floor(zf);
        let x1 = x0 + 1;
        let y1 = y0 + 1;
        let z1 = z0 + 1;

        let xs: f32;
        let ys: f32;
        let zs: f32;
        match self.interp {
            Interp::Linear => {
                xs = xf - x0 as f32;
                ys = yf - y0 as f32;
                zs = zf - z0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(xf - x0 as f32);
                ys = interp_hermite_func(yf - y0 as f32);
                zs = interp_hermite_func(zf - z0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(xf - x0 as f32);
                ys = interp_quintic_func(yf - y0 as f32);
                zs = interp_quintic_func(zf - z0 as f32);
            }
        }

        let mut lut_pos0 = self.index3d_256(offset, x0, y0, z0);
        let mut lut_pos1 = self.index3d_256(offset, x1, y0, z0);

        let mut lx0x = lerp(
            CELL_3D_X[lut_pos0 as usize],
            CELL_3D_X[lut_pos1 as usize],
            xs,
        );
        let mut ly0x = lerp(
            CELL_3D_Y[lut_pos0 as usize],
            CELL_3D_Y[lut_pos1 as usize],
            xs,
        );
        let mut lz0x = lerp(
            CELL_3D_Z[lut_pos0 as usize],
            CELL_3D_Z[lut_pos1 as usize],
            xs,
        );

        lut_pos0 = self.index3d_256(offset, x0, y1, z0);
        lut_pos1 = self.index3d_256(offset, x1, y1, z0);

        let mut lx1x = lerp(
            CELL_3D_X[lut_pos0 as usize],
            CELL_3D_X[lut_pos1 as usize],
            xs,
        );
        let mut ly1x = lerp(
            CELL_3D_Y[lut_pos0 as usize],
            CELL_3D_Y[lut_pos1 as usize],
            xs,
        );
        let mut lz1x = lerp(
            CELL_3D_Z[lut_pos0 as usize],
            CELL_3D_Z[lut_pos1 as usize],
            xs,
        );

        let lx0y = lerp(lx0x, lx1x, ys);
        let ly0y = lerp(ly0x, ly1x, ys);
        let lz0y = lerp(lz0x, lz1x, ys);

        lut_pos0 = self.index3d_256(offset, x0, y0, z1);
        lut_pos1 = self.index3d_256(offset, x1, y0, z1);

        lx0x = lerp(
            CELL_3D_X[lut_pos0 as usize],
            CELL_3D_X[lut_pos1 as usize],
            xs,
        );
        ly0x = lerp(
            CELL_3D_Y[lut_pos0 as usize],
            CELL_3D_Y[lut_pos1 as usize],
            xs,
        );
        lz0x = lerp(
            CELL_3D_Z[lut_pos0 as usize],
            CELL_3D_Z[lut_pos1 as usize],
            xs,
        );

        lut_pos0 = self.index3d_256(offset, x0, y1, z1);
        lut_pos1 = self.index3d_256(offset, x1, y1, z1);

        lx1x = lerp(
            CELL_3D_X[lut_pos0 as usize],
            CELL_3D_X[lut_pos1 as usize],
            xs,
        );
        ly1x = lerp(
            CELL_3D_Y[lut_pos0 as usize],
            CELL_3D_Y[lut_pos1 as usize],
            xs,
        );
        lz1x = lerp(
            CELL_3D_Z[lut_pos0 as usize],
            CELL_3D_Z[lut_pos1 as usize],
            xs,
        );

        *x += lerp(lx0y, lerp(lx0x, lx1x, ys), zs) * warp_amp;
        *y += lerp(ly0y, lerp(ly0x, ly1x, ys), zs) * warp_amp;
        *z += lerp(lz0y, lerp(lz0x, lz1x, ys), zs) * warp_amp;
    }

    #[allow(dead_code)]
    fn gradient_perturb(&self, x: &mut f32, y: &mut f32) {
        self.single_gradient_perturb(0, self.gradient_perturb_amp, self.frequency, x, y);
    }

    #[allow(dead_code)]
    fn gradient_perturb_fractal(&self, x: &mut f32, y: &mut f32) {
        let mut amp = self.gradient_perturb_amp * self.fractal_bounding;
        let mut freq = self.frequency;
        let mut i = 1;

        self.single_gradient_perturb(self.perm[0], amp, self.frequency, x, y);

        while i < self.octaves {
            freq *= self.lacunarity;
            amp *= self.gain;
            self.single_gradient_perturb(self.perm[i as usize], amp, freq, x, y);
            i += 1;
        }
    }

    #[allow(dead_code)]
    fn single_gradient_perturb(
        &self,
        offset: u8,
        warp_amp: f32,
        frequency: f32,
        x: &mut f32,
        y: &mut f32,
    ) {
        let xf = *x * frequency;
        let yf = *y * frequency;

        let x0 = fast_floor(xf);
        let y0 = fast_floor(yf);
        let x1 = x0 + 1;
        let y1 = y0 + 1;

        let xs: f32;
        let ys: f32;
        match self.interp {
            Interp::Linear => {
                xs = xf - x0 as f32;
                ys = yf - y0 as f32;
            }
            Interp::Hermite => {
                xs = interp_hermite_func(xf - x0 as f32);
                ys = interp_hermite_func(yf - y0 as f32);
            }
            Interp::Quintic => {
                xs = interp_quintic_func(xf - x0 as f32);
                ys = interp_quintic_func(yf - y0 as f32);
            }
        }

        let mut lut_pos0 = self.index2d_256(offset, x0, y0);
        let mut lut_pos1 = self.index2d_256(offset, x1, y0);

        let lx0x = lerp(
            CELL_2D_X[lut_pos0 as usize],
            CELL_2D_X[lut_pos1 as usize],
            xs,
        );
        let ly0x = lerp(
            CELL_2D_Y[lut_pos0 as usize],
            CELL_2D_Y[lut_pos1 as usize],
            xs,
        );

        lut_pos0 = self.index2d_256(offset, x0, y1);
        lut_pos1 = self.index2d_256(offset, x1, y1);

        let lx1x = lerp(
            CELL_2D_X[lut_pos0 as usize],
            CELL_2D_X[lut_pos1 as usize],
            xs,
        );
        let ly1x = lerp(
            CELL_2D_Y[lut_pos0 as usize],
            CELL_2D_Y[lut_pos1 as usize],
            xs,
        );

        *x += lerp(lx0x, lx1x, ys) * warp_amp;
        *y += lerp(ly0x, ly1x, ys) * warp_amp;
    }
}

#[cfg(test)]
mod tests {
    use super::{CellularDistanceFunction, FastNoise, NoiseType};

    #[test]
    // Tests that we make an RGB triplet at defaults and it is black.
    fn test_cellular_noise_overflow() {
        let mut noise = FastNoise::seeded(6000);
        noise.set_noise_type(NoiseType::Cellular);
        noise.set_frequency(0.08);
        noise.set_cellular_distance_function(CellularDistanceFunction::Manhattan);
        for y in 0..1024 {
            for x in 0..1024 {
                let frac_x = x as f32 / 1024.0;
                let frac_y = y as f32 / 1024.0;

                let cell_value_f = noise.get_noise(frac_x, frac_y);
                assert!(cell_value_f != 0.0);
            }
        }
    }
}
